对于普通的继承,不涉及虚拟继承时,比较简单。
如果继承多个父类则:
每个父类都有自己的虚表,子类虚成员函数加到第一个父类的虚函数列表后面
其它数据成员,按顺序依次放在各个父类的虚函数列表后面
以下内容,摘自网友
[0] Base1::_vptr-> [0] Derive::f() [1] Base1::g() [2] Base1::h() [3] Driver::g1() [4] 00000000 ç 注意:在GCC下,这里是1 [1] Base1.ibase1 = 10 [2] Base2::_vptr-> [0] Derive::f() [1] Base2::g() [2] Base2::h() [3] 00000000 ç 注意:在GCC下,这里是1 [3] Base2.ibase2 = 20 [4] Base3::_vptr-> [0] Derive::f() [1] Base3::g() [2] Base3::h() [3] 00000000 [5] Base3.ibase3 = 30 [6] Derive.iderive = 100 |
使用图片表示是下面这个样子:
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
———————————————————————————————————————————————————
对于有虚拟继承的情况
很高深的东西,搞不太明白,这时候似乎还有一个虚类指针
虚拟继承
即派生类继承多次基类,但在派生类中只存在一份基类的拷贝。编译器实现虚拟继承的方式并不相同,下面我结合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_data 1> | +--- 1> | +--- (base class Base2) 1> 12 | | {vfptr} 1> 16 | | {vbptr} 1> 20 | | Base2_data 1> | +--- 1> 24 | Derived_data 1> +--- 1> +--- (virtual base CommonBase) 1> 28 | {vfptr} 1> 32 | commonBase_data 1> +--- 1> 1> Derived::$vftable@Base1@: 1> | &Derived_meta 1> | 0 1> 0 | &Derived::Base1_Fun1 1> 1 | &Base1::Base1_Fun2 1> 1> Derived::$vftable@Base2@: 1> | -12 1> 0 | &Base2::Base2_Fun1 1> 1 | &Derived::Base2_Fun2 1> 1> Derived::$vbtable@Base1@: 1> 0 | -4 1> 1 | 24 (Derivedd(Base1+4)CommonBase) 1> 1> Derived::$vbtable@Base2@: 1> 0 | -4 1> 1 | 12 (Derivedd(Base2+4)CommonBase) 1> 1> Derived::$vftable@CommonBase@: 1> | -28 1> 0 | &thunk: this-=16; goto Base1::commonBaseFunc 1> 1> Derived::Base1_Fun1 this adjustor: 0 1> Derived::Base2_Fun2 this adjustor: 12 1> 1> vbi: class offset o.vbptr o.vbte fVtorDisp 1> 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首地址的偏移量。