1.爲什麼需要虛繼承
如下圖所示如果訪問Der::Fun or Der::m_nValue就會帶來二義性,無法確定是調用Base1的還是Base2的,所以爲了解決多重繼承情況下成員訪問的二義性,引入了虛繼承機制。
一般繼承:
虛繼承:
2.虛繼承實現
在虛繼承下,Der通過共享虛基類SuperBase來避免二義性,在Base1,Base2中分別保存虛基類指針,Der繼承Base1,Base2,包含Base1, Base2的虛基類指針,並指向同一塊內存區,這樣Der便可以間接存取虛基類的成員,如下圖所示:
3.不同編譯器實現方式
不同編譯器對間接存取的方法不同,以下以GCC和VC爲例,均採用以下代碼進行實驗:
view plaincopy to clipboardprint?
- class SuperBase
- {
- public:
- int m_nValue;
- void Fun(){}
- virtual ~SuperBase(){}
- };
- class Base1: virtual public SuperBase
- {
- public:
- virtual ~ Base1(){}
- };
- class Base2: virtual public SuperBase
- {
- public:
- virtual ~ Base2(){}
- };
- class Der:public Base1, public Base2
- {
- public:
- virtual ~ Der(){}
- };
1) GCC中結果爲8, 12, 12, 16
解析:sizeof(SuperBase) = sizeof(int) + 虛函數表指針
sizeof(Base1) = sizeof(Base2) = sizeof(int) + 虛函數指針 + 虛基類指針
sizeof(Der) = sizeof(int) + Base1中虛基類指針 + Base2虛基類指針 + 虛函數指針
GCC共享虛函數表指針,也就是說父類如果已經有虛函數表指針,那麼子類中共享父類的虛函數表指針空間,不在佔用額外的空間,這一點與VC不同,VC在虛繼承情況下,不共享父類虛函數表指針,詳見如下。
2)VC中結果爲:8, 16, 16, 24
解析:sizeof(SuperBase) = sizeof(int) + 虛函數表指針
sizeof(Base1) = sizeof(Base2) = sizeof(int) + SuperBase虛函數指針 + 虛基類指針 + 自身虛函數指針
sizeof(Der) = sizeof(int) + Base1中虛基類指針 + Base2中虛基類指針 + Base1虛函數指針 + Base2虛函數指針 + 自身虛函數指針
如果去掉虛繼承,結果將和GCC結果一樣,A,B,C都是8,D爲16,原因就是VC的編譯器對於非虛繼承,父類和子類是共享虛函數表指針的。
繼承中的內存佈局
1. 虛函數指針(vptr)放最前,之後放變量。
2. 多個父類排着放,再放子類
3. 子類的覆蓋的虛函數將所有祖先的同名虛函數都覆蓋。
4. 子類其它的虛函數指針放在第一個父類的虛函數表裏。
5. 虛擬繼承的情況只需要在鑽石繼承中有必要使用(避免二義性),子類中最先的祖先放最後。
多說不如舉例,看下面轉的文章。
本文章轉自:http://blog.csdn.net/randyjiawenjie/article/details/6693337
分爲四種情況:
1.單繼承
2.多繼承(不含鑽石繼承)
3.非虛繼承的鑽石繼承
4.虛繼承的鑽石繼承
注:下面所有類中的函數都是虛函數。
1.單繼承
單繼承體系如下:
GrandChild對象的內存佈局:
可見以下幾個方面:
1)虛函數表在最前面的位置。
2)成員變量根據其繼承和聲明順序依次放在後面。
3)在單一的繼承中,被overwrite的虛函數在虛函數表中得到了更新。
2.多繼承
多繼承的體系如下:
Derive對象的內存佈局如下:
我們可以看到:
1) 每個父類都有自己的虛表。
2) 子類的成員函數被放到了第一個父類的表中。
3) 內存佈局中,其父類佈局依次按聲明順序排列。
4) 每個父類的虛表中的f()函數都被overwrite成了子類的f()。這樣做就是爲了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。
出現鑽石繼承的虛繼承的時候,虛基類在子類中只有一份。
出現鑽石繼承的非虛繼承的時候,虛基類在每個子類中都有一份。
3.非虛繼承的鑽石繼承
繼承體系如下:
D的內存佈局如下:
紅色的部分就是重複的部分,就會造成二義性
4.虛繼承的鑽石繼承
(虛繼承就是解決鑽石繼承問題的,如果不存在鑽石繼承,就不用虛繼承)
繼承體系如下:(紅色專門標準虛繼承)
D的內存佈局如下:
可以看出,少了重合的部分。但是,代價是增加了一個虛函數指針。