但是vtbl英文原文是virtual table并非virtual function table
所以当类本身与其直接间接基类内都未定义任何虚函数时也是有可能有虚表的
因为非虚继承第一直接基类Base1与派生类Derived的基地址是一致的
因为Derived的vtbl和Base1的vtbl已经融为一体了
也就是说从Derived的vtbl中完全可以查到从Base1继承下来的虚函数
事实上图中所示的Base2::vptr并不是指向Base2类的vtbl
1.跳转到vtbl中正确的虚函数(也就是Derived的虚函数)地址所在的内存单元
2.修改this指针使其指向Derived对象,并传入上一步检索到的虚函数中
执行thunk后跳转到vtbl中Derived::~Derived()所在的槽位
从而实现了Base2 *pb2和事实对象类Derived的动态绑定
由此我们不妨思考一下为何动态绑定需要用指针或引用而不能通过实例对象来实现
这里相当于拷贝了d对象中的Base2::vptr+Base2::data到新对象中
并且修改原先指向Dervied::vtbl的vptr,使其重新指向Bae2::vtbl
原来使用Base2 *pb2时,Base2::vptr指针仍旧指向Dervied::vtbl中(的thunk码)
而赋值给b2对象后,Base2::vptr指向的就是一个完全不同类的虚表Base2::vtbl了
最后Derived类自己定义的成员会放在所有直接基类成员的后面
|
1. 虚基类Base的内存空间位于Dervied类成员的下面
才得以实现虚基类在所有子子孙孙派生类都共享一个共同虚基类的语法特性
此时不论是把这个Derived对象赋予Base1指针还是Base2指针
大家可以看到C++标准委员会和编译器大厂设计这一套布局的良苦用心
他们并未因为Derived::vptr在Base1的子对象域中而就盲目地把Derived::data也挪过去
this+=sizeof(Base1+Base2+...+Base[N-1])即可指向BaseN的起始位置
非常方便 当然this是个Derived * const类型 所以不能被修改
-----------------------------------------------------
在动态绑定也就是多态的实现中thunk技术发挥了至关重要的作用
编译器会先把Base1的虚函数指针按声明顺序写在对应偏移的槽位上
2.替换Base1中相应被覆盖的虚函数为Derived类中的虚函数
3.而派生类除了覆盖了第一直接基类的虚函数外 还覆盖了后继直接基类的
于是将后继直接基类被覆盖的虚函数补到第一直接基类虚函数区域的后面
所以大家会在后面看到覆盖了Base2的Derived::v3()
4.接着添上了Derived类的type_info类对象的地址
也就是说dynamic_cast和typeid这俩运算符是通过type_info对象实现的
1.表结构保护了Base1和Base2这些直接基类的vtbl结构
按照相应基类中虚函数的声明顺序完成固定偏移就能寻址到想要的虚函数