轉自:http://blog.csdn.net/xsh_123321/article/details/5956289
1.為什么需要虛繼承
如下圖所示如果訪問Der::Fun or Der::m_nValue就會帶來二義性,無法確定是調用Base1的還是Base2的,所以為了解決多重繼承情況下成員訪問的二義性,引入了虛繼承機制。
一般繼承:
虛繼承:
2.虛繼承實現
在虛繼承下,Der通過共享虛基類SuperBase來避免二義性,在Base1,Base2中分別保存虛基類指針,Der繼承Base1,Base2,包含Base1, Base2的虛基類指針,並指向同一塊內存區,這樣Der便可以間接存取虛基類的成員,如下圖所示:
3.不同編譯器實現方式
不同編譯器對間接存取的方法不同,以下以GCC和VC為例,均采用以下代碼進行實驗:
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的內存布局如下:
可以看出,少了重合的部分。但是,代價是增加了一個虛函數指針。