对于多继承情况
考虑示例代码
1 2 3 |
|
有如下内存布局
首先出现的是派生类Derived类的虚表指针vptr
(这里插入一个提醒:
一直以来vptr都被国人翻译为虚函数表指针
但是vtbl英文原文是virtual table并非virtual function table
为什么呢
因为这个表不只是为了虚函数而准备的
一切虚拟化技术都会用这个表 包括 虚继承 RTTI等
所以当类本身与其直接间接基类内都未定义任何虚函数时也是有可能有虚表的
典型就是当前类继承了一个虚基类..提醒结束)
其次是第一直接基类的数据成员
然后是第二直接基类的虚表指针vptr
再次是第二直接基类的数据成员
如果还有后继直接基类 那么依此类推
也许有朋友会困惑为何偏移0处是Derived类的vptr
而不是Base1的vptr
因为非虚继承第一直接基类Base1与派生类Derived的基地址是一致的
Derived的内存结构正是从Base1开始
而对于
1 2 |
|
这样的多态引用
事实上我们也是用的Derived的虚表vtbl
因为Derived的vtbl和Base1的vtbl已经融为一体了
也就是说从Derived的vtbl中完全可以查到从Base1继承下来的虚函数
以及覆盖Base1的虚函数
而对于Base2来说
事实上图中所示的Base2::vptr并不是指向Base2类的vtbl
而是指向Derived类的vtbl中的一个thunk地址
这个所谓的thunk地址又指向一段汇编码
这段thunk汇编码负责做两件事:
1.跳转到vtbl中正确的虚函数(也就是Derived的虚函数)地址所在的内存单元
2.修改this指针使其指向Derived对象,并传入上一步检索到的虚函数中
通过这个thunk策略
编译器实现了C++的多态性
举个例子
1 2 |
|
此时使用pb2调用虚析构函数时
通过Base2::vptr跳转到了thunk汇编代码
执行thunk后跳转到vtbl中Derived::~Derived()所在的槽位
然后把当前指向Base2子对象部分的的this指针
添加适当偏移使其指向Derived对象的内存首地址
然后传入并调用Derived::~Derived()
从而实现了Base2 *pb2和事实对象类Derived的动态绑定
由此我们不妨思考一下为何动态绑定需要用指针或引用而不能通过实例对象来实现
已知
1 2 |
|
这里相当于拷贝了d对象中的Base2::vptr+Base2::data到新对象中
并且修改原先指向Dervied::vtbl的vptr,使其重新指向Bae2::vtbl
这可区别大了
原来使用Base2 *pb2时,Base2::vptr指针仍旧指向Dervied::vtbl中(的thunk码)
而赋值给b2对象后,Base2::vptr指向的就是一个完全不同类的虚表Base2::vtbl了
回到正题
最后Derived类自己定义的成员会放在所有直接基类成员的后面
但是这个说法是有待商榷的
下面的例子会进一步说明
对于虚继承+多继承的套路有些许变招
考虑示例代码
1 2 3 4 |
|
有如下内存布局
此时注意到几个变化:
1. 虚基类Base的内存空间位于Dervied类成员的下面
也就是位于整个内存空间的最后
即使它是所有类的共同祖先
大家不妨进一步思考这样做的目的
正常来说 非直接基类是不该出现在派生类内存中的
但编译器正是通过这样的举措
才得以实现虚基类在所有子子孙孙派生类都共享一个共同虚基类的语法特性
此时不论是把这个Derived对象赋予Base1指针还是Base2指针
他们都只需要把按照自己既定的惯例加上一个固定的偏移
就能在对象的尾部访问到那个唯一的虚基类
2.Derived的成员事实上是位于所有非虚直接基类的后面
位于所有继承链上的虚基类的前面
大家可以看到C++标准委员会和编译器大厂设计这一套布局的良苦用心
他们并未因为Derived::vptr在Base1的子对象域中而就盲目地把Derived::data也挪过去
而是把data放在了所有非虚直接基类的后面
这样就保证了其每个非虚直接基类的完整性和模块化
并且方便在动态绑定时计算指针偏移
例如
1 2 |
|
此时只需要令
this+=sizeof(Base1+Base2+...+Base[N-1])即可指向BaseN的起始位置
非常方便 当然this是个Derived * const类型 所以不能被修改
这里只是意会转为汇编后的寄存器操作
3.有一个需要特别注意的地方
就是如果继承链上的某个类既没有虚指针也没有数据成员
也就是说它没有内存分配
这个时候就要小心了
其指针将指向Derived对象的首地址!
也就是该类的指针将会与pb1、pd存储同样的地址
这样做的原因就是避免它指向位于其指定内存区域下方的
一个与他无关的对象的首地址