C++ 中的虚函数表及虚函数执行原理
为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的。虚函数表在动态/延迟绑定行为中用于查询调用的函数。
尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的。
首先,每个包含虚函数的类(或者继承自的类包含了虚函数)都有一个自己的虚函数表。这个表是一个在编译时确定的静态数组。虚函数表包含了指向每个虚函数的函数指针以供类对象调用。
其次,编译器还在基类中定义了一个隐藏指针,我们称为 *__vptr,*__vptr 是在类实例创建时自动设置的,以指向类的虚函数表。*__vptr 是一个真正的指针,这和 *this 指针不同,*this 指针实际是一个函数参数,使编译器来达到自引用的目的。
结果就是,每个类对象都会多分配一个指针的大小,并且 *__vptr 是被派生类继承的。
如果你不清楚这些组件是怎么配合运作的,看下面的例子:
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
因为这里有 3 个类,编译器会创建 3 个虚函数表。
然后编译器会在使用了虚函数的最上层基类中定义一个隐藏指针。尽管这个过程编译器会自动处理,但我们还是通过下面的例子来说明指针添加的位置:
class Base
{
public:
FunctionPointer *__vptr;
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
*__vptr 在类对象创建的时候会设置成指向类的虚函数表。例如,类型 Base 被实例化的时候,*__vptr 就指向 Base 的虚函数表。类型 D1 或者 D2 被实例化的时候,*__vptr 就指向 D1 或者 D2 的虚函数表。
现在我们来看下虚函数表是怎么创建的。因为示例中每个类仅有 2 个虚函数,所以每个虚函数表会存放两个函数指针(分别指向 function1() 和 function2())。
Base 对象的虚函数表最简单。Base 对象只能访问 Base 类型的成员,不能访问 D1 或者 D2 的函数。所以 Base 的虚函数表中的两个指针分别指向 Base::function1() 和 Base::function2()。
D1 的虚函数表稍复杂点,D1 对象能够访问 D1 以及 Base 的成员。D1 重写了 function1(),但没有重写 function2(),所以 D1 的虚函数表中的两个指针分别指向 D1::function1() 和 Base::function2()。
D2 的虚函数表同理 D1,包含了分别指向 Base::function1() 和 D2::function2() 的指针。

考虑如果创建 D1 对象时会发生什么:
int main()
{
D1 d1;
}
因为 d1 是 D1 类型对象,d1 有它自己的 *__vptr 指向 D1 类型的虚函数表。
现在创建一个 Base 类型指针 *dPtr 指向 d1:
int main()
{
D1 d1;
Base *dPtr = &d1;
return 0;
}
重点:
因为
dPtr是Base类型指针,它只指向d1对象的Base类型部分(即,指向d1对象中的Base子对象),而*__vptr也在Base类型部分。所以dPtr可以访问Base类型部分中的*__vptr。同时,这里注意,dPtr->__vptr指向的是D1的虚拟函数表,这是在d1初始化时就确定的。所以结果,尽管dPtr是Base类型指针,但它能够访问D1的虚函数表。
因此,当有调用 dPtr->function1() 时,发生了什么?
int main()
{
D1 d1;
Base *dPtr = &d1;
dPtr->function1();
return 0;
}
首先,程序识别到 function1() 是一个虚函数。
其次,程序使用 dPtr->__vptr 获取到了 D1 的虚函数表。
然后,它在 D1 的虚函数表中寻找可以调用的 function1() 版本,这里是 D1::function1()。
因此,dPtr->function1() 实际调用了 D1::function1()。
通过虚函数表,编译器和程序能够确定调用什么版本的虚函数,尽管使用的是指向/引用基类的指针或者引用。
调用虚函数会比调用非虚函数更慢,有以下几个原因:
- 必须使用
*__vptr获取正确的虚函数。 - 必须建立虚函数表的索引来获取想要调用的函数。
- 调用找到的函数。
结果就是必须进行三次操作才能完成对函数的调用。但是对于现代计算机系统,这些额外操作增加的时间几乎可以忽略不计。
另外,每个使用虚函数表的类都有 *__vptr 指针,从而每个类对象都会多一个指针的空间。虚函数很强大,但是它确实产生了性能开销。
C++ 中的虚函数表及虚函数执行原理的更多相关文章
- C++对象的内存布局以及虚函数表和虚基表
C++对象的内存布局以及虚函数表和虚基表 本文为整理文章, 参考: http://blog.csdn.net/haoel/article/details/3081328 http://blog.csd ...
- C++虚函数表与虚析构函数
1.静态联编和动态联编联编:将源代码中的函数调用解释为要执行函数代码. 静态联编:编译时能确定唯一函数.在C中,每个函数名都能确定唯一的函数代码.在C++中,因为有函数重载,编译器须根据函数名,参数才 ...
- 虚函数表-C++多态的实现原理
目录 1.说明 2.虚函数表 3.代码示例 参考:http://c.biancheng.net/view/267.html 1.说明 我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是 ...
- C++中的虚函数表
(感谢http://blog.csdn.net/haoel/article/details/1948051/) C++中的虚函数的作用主要是实现了多态的机制. 多态,简而言之就是用父类型别的指针指向其 ...
- C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
- C++中的虚函数以及虚函数表
一.虚函数的定义 被virtual关键字修饰的成员函数,目的是为了实现多态 ps: 关于多态[接口和实现分离,父类指针指向子类的实例,然后通过父类指针调用子类的成员函数,这样可以让父类指针拥有多种形态 ...
- C++ 虚函数表解析
转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...
- C++ 知道虚函数表的存在
今天翻看陈皓大大的博客,直接找关于C++的东东,看到了虚函数表的内容,找一些能看得懂的地方记下笔记. 0 引子 类中存在虚函数,就会存在虚函数表,在vs2015的实现中,它存在于类的头部. 假设有如下 ...
- C++虚函数与虚函数表
多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说 ...
随机推荐
- Acwing 154 滑动窗口(单调队列)经典模板
给定一个大小为n≤106n≤106的数组. 有一个大小为k的滑动窗口,它从数组的最左边移动到最右边. 您只能在窗口中看到k个数字. 每次滑动窗口向右移动一个位置. 以下是一个例子: 该数组为[1 3 ...
- Musical Theme POJ - 1743 后缀数组
A musical melody is represented as a sequence of N (1<=N<=20000)notes that are integers in the ...
- Linux 搭建网站
wget http://dl.wdlinux.cn/lanmp_laster.tar.gz tar zxvf lanmp_laster.tar.gz sh lanmp.sh https://www.w ...
- 对模拟器虚假设备识别能力提升15%!每日清理大师App集成系统完整性检测
前言 每日清理大师是一款智能便捷的手机清理软件,可快速清理无用缓存.垃圾文件和应用残留,还可深度清理如社交软件中的无用缓存等,有效解决手机卡顿.耗电快.内存不足等问题.每日清理大师App在结合了系统完 ...
- Java15变量竟然没什么区别,八大基本数据类型你知道吗?
变量是什么? 变量是用来为不同数据类型在内存中分配的空间用来储存该数据. 不同于python这样的弱类型语言,变量声明不需要定义数据类型,就和写数学方程式一般,谁等于谁即可.而Java这个发展了多个版 ...
- hihocoder 1631
时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 There are many homeless cats in PKU campus. They are all happy ...
- JavaScript常见笔试题分析
1.Javascript的typeof可能返回的结果有哪些? 答:共6种,具体为number ,boolean,string,undefined,function,object(对象或者null返 ...
- 记一次getshell
水文涉及的知识点: Oday的挖掘 可以执行命令,但是有WAF , 命令执行的绕过 机器不出网,无法反弹 Echo写文件,发现只要写入php文件,后缀就重名为*,如1.php 变成1.* 通过上传 l ...
- μC/OS-III---I笔记7---消息队列
消息队列 任务之间仅仅靠信号量进行"沟通"是不够的,信号量可以标志事件的发生,却无法传递更多的数据,在需要任务间的数据信息传递时就绪要用到消息队列,传统我们一般在前后太系统中都是通 ...
- 如何在 Apple Watch S6上离线播放音乐
如何在 Apple Watch S6上离线播放音乐 Apple Watch 离线播放音乐 营销策略,捆绑销售 Apple Watch + AirPods + Apple Music Apple Wat ...