虚拟继承
即派生类继承多次基类,但在派生类中只存在一份基类的拷贝。编译器实现虚拟继承的方式并不相同,下面我结合VS2010来探讨C++虚拟继承。声明一个虚基类CommonBase,两个从虚基类虚拟派生Base1和Base2,然后D,公有多继承自Base1和Base2,具体类定义如下:
class CommonBase{public: virtual void commonBaseFunc() = 0;private: int commonBase_data;};class Base1 : public virtual CommonBase{public: virtual void Base1_Fun1(){} virtual void Base1_Fun2(){} virtual void commonBaseFunc(){}private: int Base1_data;};class Base2 : public virtual CommonBase{public: virtual void Base2_Fun1(){} virtual void Base2_Fun2(){}private: int Base2_data;};class Derived : public Base1, public Base2{public: virtual void Base1_Fun1(){} virtual void Base2_Fun2(){}private: int Derived_data;};
现在我们来看看Derived类对象的内存布局,编译之后,我们在生成窗口看到如下信息:
1> class Derived size(36):1> +---1> | +--- (base class Base1)1> 0 | | {vfptr}1> 4 | | {vbptr}1> 8 | | Base1_data1> | +---1> | +--- (base class Base2)1> 12 | | {vfptr}1> 16 | | {vbptr}1> 20 | | Base2_data1> | +---1> 24 | Derived_data1> +---1> +--- (virtual base CommonBase)1> 28 | {vfptr}1> 32 | commonBase_data1> +---1> 1> Derived::$vftable@Base1@:1> | &Derived_meta1> | 01> 0 | &Derived::Base1_Fun11> 1 | &Base1::Base1_Fun21> 1> Derived::$vftable@Base2@:1> | -121> 0 | &Base2::Base2_Fun11> 1 | &Derived::Base2_Fun21> 1> Derived::$vbtable@Base1@:1> 0 | -41> 1 | 24 (Derivedd(Base1+4)CommonBase)1> 1> Derived::$vbtable@Base2@:1> 0 | -41> 1 | 12 (Derivedd(Base2+4)CommonBase)1> 1> Derived::$vftable@CommonBase@:1> | -281> 0 | &thunk: this-=16; goto Base1::commonBaseFunc1> 1> Derived::Base1_Fun1 this adjustor: 01> Derived::Base2_Fun2 this adjustor: 121> 1> vbi: class offset o.vbptr o.vbte fVtorDisp1> CommonBase 28 4 4 0
从输出的信息来看,虚拟继承有别于非虚拟继承的一个区别就是把虚基类放在整个类对象的末尾,而非虚拟继承则是按照继承的顺序先存放父类,再到子类。下面我们就来说说这些信息究竟表示什么含义。
对于size(36)就指明Derived对象的大小为36个字节,我们看看Base2的vftable,其中-12表示Base2在Derived类中的偏移量。Base1的vbtable中-4表示Base1的vbtable与Base1这个对象的偏移量,因为一般都是把vftable放在对象的前面,所以vbtable与对象首地址的偏移量一般就是中间隔着vftable,24表示Base1的vbtable与虚基类CommonBase首地址的偏移量,从上图可以看出Base1的vbtable与Derived的偏移量为4,CommonBase与Derived的偏移量为28,这24加上4就刚好等于28,同理,Base2也一样。
我们着重来看一下,CommonBase的vftable,-28就不用多说了,表示CommonBase相对于Derived首地址的偏移量,重点是这句&thunk: this-=16;goto Base1::commonBaseFunc,这句就说明VS2010使用了thunk技术来实现虚拟继承的时候,虚基类CommonBase只存在一份拷贝
在Derived,由于Base1已经重写了CommonBase的虚函数commonBaseFunc(),所以当Derived调用commonBaseFunc()函数时才会跳转到
Base1去执行。
Derived::Base2_Fun2 this adjustor:12表示当一个Derived对象d调用Base2的Base2_Fun2()函数时,this指针需要跳转的偏移量,从图上可以看出12就是Base2相对于Derived首地址的偏移量。