這次我們看看菱形結構的虛繼承。虛繼承的引入本就是為瞭解決複雜結構的繼承體系問題。上一篇我們在討論虛繼承時用的是一個簡單的繼承結構,只是為了打個鋪墊。
我們先看看這幾個類,這是一個典型的菱形繼承結構。C100和C101通過虛繼承共用同一個父類C041。C110則從C100和C101多重繼承而來。
struct C041
{
C041() : c_(0x01) {}
virtual void foo() { c_ = 0x02; }
char c_;
};
struct C100 : public virtual C041
{
C100() : c_(0x02) {}
char c_;
};
struct C101 : public virtual C041
{
C101() : c_(0x03) {}
char c_;
};
struct C110 : public C100, public C101
{
C110() : c_(0x04) {}
char c_;
};
運行如下代碼:
PRINT_SIZE_DETAIL(C110)
結果為:
The size of C110 is 16
The detail of C110 is 28 c3 45 00 02 1c c3 45 00 03 04 18 c3 45 00 01
我們可以象上一篇一樣,畫出對象的記憶體布局。
|C100,5 |C101,5 |C110,1 |C041,5 |
|ospt,4,11 |m,1 |ospt,4,6 |m,1 |m,1 |vtpt,4 |m1 |
(註:為了不折行,我用了縮寫。ospt代表位移值指標、m代表成員變數、vtpt代表虛表指標。第一個數字是該地區的大小,即位元組數。只有位移值指標有第二個數字,第二個數字就是位移值指標指向的位移值的大小。)
可以看到對象的記憶體布局中只有一個C041,即祖父類的部分只有一份,且放在最後面。這就是菱形繼承。對比前面幾篇的討論,我們可以知道,如果沒有用虛繼承機制,那麼在C041對象的記憶體布局中會出現兩份C041部分,這也就是所謂的V型繼承。相應的物件版面配置為:C041+C100+C041+C101 +C110。在V型繼承中是不能直接從C110,即孫子類,直接轉型到C041,即祖父類的。因為在對象的布局中有兩份祖父類的實體,一份從C100而來,一份從C101而來。編譯器在決議時會存在二義性,它不知道轉型後到底用哪一份實體。雖然可以通過先轉型到某一父類,然後再轉型到祖父類來解決。但使用這種方法時,如果改寫了祖父類的成員變數的內容,runtime是不會同步兩個祖父類實體的狀態,因此可能會有語義錯誤。