虚函数表介绍

多态是由虚函数实现的,而虚函数主要是通过虚函数表(V-Table)来实现的。

如果一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表,虚函数表存储的每一项是一个虚函数的地址。如下图:

这里写图片描述

这个类的每一个对象都会包含一个虚指针(虚指针存在于对象实例地址的最前面,保证虚函数表有最高的性能),这个虚指针指向虚函数表。

注:对象不包含虚函数表,只有虚指针,类才包含虚函数表,派生类会生成一个兼容基类的虚函数表。

· 原始基类的虚函数表: 
  下图是原始基类的对象,可以看到虚指针在地址的最前面,指向基类的虚函数表(假设基类定义了3个虚函数) 
这里写图片描述

· 单继承时的虚函数(无重写基类虚函数) 
假设现在派生类继承基类,并且重新定义了3个虚函数(是派生类自己的虚函数),派生类会自己产生一个兼容基类虚函数表的属于自己的虚函数表。 
这里写图片描述

Derive class 继承了 Base class 中的三个虚函数,准确的说,是该函数实体的地址被拷贝到 Derive类的虚函数表,派生类新增的虚函数置于虚函数表的后面,并按声明顺序存放。

· 单继承时的虚函数(重写基类虚函数) 
现在派生类重写基类的x函数,可以看到这个派生类构建自己的虚函数表的时候,修改了base::x()这一项,指向了自己的虚函数。 
这里写图片描述

· 多重继承时的虚函数(Derived ::public Base1,public Base2) 
这个派生类多重继承了两个基类base1,base2,因此它有两个虚函数表。 
这里写图片描述 
  

  它的对象会有多个虚指针(据说和编译器相关),指向不同的虚函数表。

  多重继承时指针的调整:

Derive b;
Base1* ptr1 = &b;   // 指向 b 的初始地址
Base2* ptr2 = &b;   // 指向 b 的第二个子对象

因为 Base1 是第一个基类,所以 ptr1 指向的是 Derive 对象的起始地址,不需要调整指针(偏移)。

因为 Base2 是第二个基类,所以必须对指针进行调整,即加上一个 offset,让 ptr2 指向 Base2 子对象。

当然,上述过程是由编译器完成的。

Base1* b1 = (Base1*)ptr2;
b1->y();                   // 输出 Base2::y()
Base2* b2 = (Base2*)ptr1;
b2->y();                   // 输出 Base1::y()

其实,通过某个类型的指针访问某个成员时,编译器只是根据类型的定义查找这个成员所在偏移量,用这个偏移量获取成员。 
由于 ptr2 本来就指向 Base2 子对象的起始地址,所以b1->y()调用到的是Base2::y(),而 ptr1 本来就指向 Base1 子对象的起始地址(即 Derive对象的起始地址),所以b2->y()调用到的是Base1::y()。

猜你喜欢

转载自blog.csdn.net/qq_42717879/article/details/81353269