几个疑问
- delete时怎么判断是子还是父的,大小如何确定?
- 多继承时的内存分布,构造、析构、调用的方式。
分析代码:
反汇编
|
|
A的虚表:
B的虚表:
D的虚表:
C的虚表:
解答
非有虚构函数的类delete直接编译为先析构之后调用free。
分配时记录了大小使用指针就可以确定,因此这个大小是与指针动态绑定的,具体做法可以存数据结构或直接用指针前一部分内存记录。因此只要确定是分配时的指针就可以一口气释放。
有虚构函数的类delete则通过虚表调用代理的析构函数。该代理先执行析构再调用free。
之前分析虚表时多出的是代理析构函数,就是给delete用的,这样delete的指针变为动态调用。
多继承时的对象内存分布:高到低
C的虚表A部分指针
A的成员
C的虚表B部分指针
B的成员
C的成员
C的虚表:
A配合部分的虚表,同时有C的部分
-A的虚函数与C覆盖的虚函数
-C的析构
-C的代理析构
-C的新虚函数
-C中覆盖B的虚函数但是A没有的虚函数
B配合部分的虚表
-代理的c覆盖b的虚函数或没有代理的c没实现b的虚函数
-代理的析构
-代理代理的析构
构造函数:
构造函数先对this调用A构造,再将this偏移到B的虚表指针部分调用B构造,再将原this的A虚表指针与B虚表指针分别指向C虚表的对应A、B部分。
使用:
将C对象赋给A时,直接用this使用,因为A的对象与虚表都符合C的分配。此时与直接使用C指针使用C一样。A使用A的方法还是C重载后的方法都是对this的所以没问题。A的方法只对this的A部分,C重载的方法针对C的整个this。此时与正常继承一样。
将C对象赋给B时,编译器可以识别出静态类型C到B的转化,因此将C的指针偏移到B的虚表指针位置赋给B的指针。此时B使用B的未被重载方法是不用代理的,因为内存布局偏移后与B一样,当B使用被C重载的方法,则使用代理再调用C的重载函数,需要在代理中将this偏移回原this使得C的方法作用于整个this。同样为B的方法针对B的部分,C的方法针对整体。
其次,虚表中C覆盖B的虚函数在A部分也存在,不过被当成C中A没有的虚函数存在虚表位置。
析构函数:
delete 有虚构函数的类的指针被编译为调用虚表项,这与普通类不同。该虚表项是编译器自动生成的析构代理类,代理类先直接调用析构函数,再delete this,C的析构函数先析构成员,再移动this调用B的析构,再调用A的。
对A指针指向C对象调用析构能直接使用C覆盖版本的正确析构,对B指针指向C对象的版本则需要再进行一次代理,将指针调回来。