在理解虚函数之前,请了解以下对象模型:
对象模型: c具有两种类型的数据成员:静态和非静态,以及三种类型的成员函数: classmemberfunctions3360static、nonstatic和virtual:
说明:使用未继承的c对象模型。
非静态数据成员位于每个类对象上,静态数据成员位于类对象之外。 静态函数和非静态函数也位于类对象之外,但对于virtual函数,虚函数表中的虚指针支持,如下所示:
每个类都生成一个称为虚拟表(virtual table,简称vtbl )的表。 虚拟表包含指向该类的所有虚拟函数的一堆指针。 虚拟表中的函数地址按声明的顺序排列,但子类中有多个重载函数时例外。
每个类对象都有一个虚拟表指针(vptr ),由编译器生成。 所有虚拟表指针的设置和重置都由类的复制控件完成,即构造函数、析构函数和赋值操作符。 vptr的位置由编译器决定,以前放在要显示声明的所有成员之后,但现在许多编译器都将vptr放在一个类对象的开头。
此外,虚函数表前面还有一个指向type_info的指针,支持运行时类型识别(rtti )。 RTI是为多态性生成的信息,仅生成具有虚函数的对象,如对象继承关系、对象自身的描述等。
圆表示类中每个对象可以共享的部分,矩形表示每个对象自己拥有的部分。
个人资料
虚函数(virtual )是c实现多态性所采用的方法,使用的是动态绑定技术虚函数表。 包括虚函数在内的所有类都有虚表。 如果基类包含虚函数,则派生类也需要自己的虚表。 通过使用这些虚函数表,即使使用基类指针调用函数,也可以正确调用正在运行的实际对象的虚函数。
例如,a类包含虚函数vfunc1、vfunc2,a类包含虚函数,因此a类具有虚表。
类a {
公共:
虚拟语音功能1 (;
虚拟语音功能2 (;
void func1(;
void func2(;
隐私:
int m_data1,m_data2;
(;
然后,a类的虚表(虚表只包含虚函数)如下:
虚表其实是指针数组,每个元素都是指向虚函数的指针。 普通函数是非虚函数,不需要通过虚表。 虚表中条目虚函数指针的赋值发生在编译器的编辑阶段,在代码编译时已经构建。
2 .虚拟表指针
虚拟表属于类,不属于特定对象。 一个班有一个虚设表就可以了。 同一类的对象使用同一虚拟表。 为了为对象指定虚拟表,对象内部包含指向您正在使用的虚拟表的指针。 为了使类(包括虚拟表)中的所有对象都具有虚拟表指针,编译器向类中添加了指针*__vptr。 当创建指向虚拟表的对象时,指针将自动设置为指向类的虚拟表。
说明:如果继承类的基类包含虚函数,则继承类也有自己的虚表,因此继承类的对象也包含指向虚表的虚表指针。
4 .动态绑定
类a {
公共:
虚拟语音功能1 (;
虚拟语音功能2 (;
void func1(;
void func2(;
隐私:
int m_data1,m_data2;
(;
class b :公共a {
公共:
虚拟语音功能1 (;
void func1(;
隐私:
int m_data3;
(;
class c :公共b {
公共:
虚拟语音功能2 (;
void func2(;
隐私:
int m_data1,m_data4;
(;
对象模型图:
说明: a是基类,b继承a,c又继承b。 因为三个类都有虚函数,所以编译器为每个类创建一个虚表。 每个类中的每个对象都有一个虚拟表指针,指向其所属类的虚拟表。
因为a类包含两个虚函数,所以A vtbl包含两个指针,分别指向A:vfunc1()和A:vfunc2)。
b类是
类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。
法则:对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向器继承的最近一个类的虚函数
5.父类通过虚表指针访问子类的虚函数
int main()
{
B bObject;
A *p = & bObject;
}
说明:bObject是类B的一个对象,故bObject包含一个虚表指针。声明一个类A的指针指向对象bObject。
以上代码执行步骤:
根据虚表指针p->__vptr来访问对象bObject对应的虚表。虽然指针p是基类A*类型,但是*__vptr也是基类的一部分,所以可以通过p->__vptr可以访问到对象对应的虚表。
在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目。
根据虚表中找到的函数指针,调用函数。从图3可以看到,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。
《-------》 指针p是基类A*类型,但是*__vptr也是基类的一部分的理解。
6.静态绑定和动态绑定,运行时多态,编译时多态
把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。 动态绑定的三个条件:
通过指针来调用函数
指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast,可以参考本文的参考资料)
调用的是虚函数
符合三个条件,编译器就会把该函数调用编译为动态绑定,调用时走虚表的机制。