虛擬繼承
即派生類繼承多次基類,但在派生類中只存在一份基類的拷貝。編譯器實現虛擬繼承的方式並不相同,下面我結合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首地址的偏移量。