上篇提到了虚函数表浅谈C++虚函数表,在对其有一定了解之后继续深入理解C++中对象的内存布局,也就是虚继承和它相关的虚基类表。
虚继承
虚继承的出现是为了解决和多重继承相关的问题,假设有这样的一系列继承关系:
class A;class B1:public A;class B2:public A;class D:public B1,public B2;
此时继承关系为一种菱形,如图右边所示:
然而这样的话,D对象则包含两个A子对象,当调用A类子对象时就会产生二义性,因此引入虚继承的概念:
class A;
class B1:public virtual A;
class B2:public virtual A;
class D:public B1,public B2;
也就是上图左边所示。
这样子的话B1和B2共享一个A子对象。
随着虚继承的引入,出现了虚基类A,那么此时D对象的内存分布也出现了变化,也就是D中的A子对象从B1和B2中独立出来了,成为单独的一片内存区域,所以为了让B1和B2能快速找到A子对象,B1和B2的虚函数表指针后面还跟了 一个虚基类表指针,指向了A子对象的相对偏移量。
在VS2010上进行了实验:
(操作小技巧:
通过VS的提示命令工具,输入cl /d1 reportSingleClassLayoutD st.cpp >> aaa.txt,可以查看对象的内存分布情况;
在变量监控窗口可以查看虚基类表的地址以及函数地址等,可以通过内存窗口查看;
typedef void(*Fun)(void);可以定义Fun的函数指针,通过类似于(Fun)*((int*)*((int*)(&pD)))等操作来对函数进行调用查看。
)
class D1: virtual public B;
class D2: virtual public B;
class GD: public D1, public D2;
大概的一个分布图如下所示:
以上为简单图示,其实在vbtable中含有该对象偏移量和子对象偏移量,如下图所示(图为网上获得,自己对类名进行转换):
虚基类表的第一项记录着当前子对象相对与虚基类表指针的偏移,这里是-4,如果该类没有定义虚函数则是0,接下来才是记录了被继承的虚基类子对象相对于虚基类表指针的偏移量,也就是指向了虚基类开头和该虚基类表指针的偏移量。
PS:这里还有一个问题,也就是如果D1类中定义了自己的构造函数,那么GD对象则会多出4字节,称为vtordisp空间,这个太复杂了,不能理解。