c 的虛擬繼承 的一些思考吧

2021-06-19 09:13:51 字數 3150 閱讀 9906

虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如:類d繼承自類b1、b2,而類b1、b2都繼承自類a,因此在類d中兩次出現類a中的變數和函式。為了節省記憶體空間,可以將b1、b2對a的繼承定義為虛擬繼承,而a就成了虛擬基類。實現的**如下:

class a

class b1:public virtual a;

class b2:public virtual a;

class d:public b1,public b2;

虛擬繼承在一般的應用中很少用到,所以也往往被忽視,這也主要是因為在c++中,多重繼承是不推薦的,也並不常用,而一旦離開了多重繼承,虛擬繼承就完全失去了存在的必要因為這樣只會降低效率和占用更多的空間。

前端時間看到這樣幾個面試題目:

第一種情況:         第二種情況:          第三種情況            第四種情況:

class a           class a              class a              class a

;              };                  char x;              char x;

class b:public virtual a   class b :public a           };                };

;              };                  virtual void foo();        virtual void foo();

};                };

如果對這四種情況分別求sizeof(a),  sizeof(b)。結果是什麼樣的呢?

第一種:4,12

第二種:4,4

第三種:8,16

第四種:8,8 

有人很堅定的說就是這個結果。但是我這裡要指出的是沒有絕對的事情,不同的編譯器或者版本不同,你的執行結果是不同的,但是執行結果的第乙個sizeof(a)相信大家都沒有意見,但是第二個,我相信有很多人都在苦惱那麼這裡我給大家先分享一下vc(vs)編譯器和gcc編譯器 的處理機制:

1、 單個虛擬繼承

(1)vs編譯器:無論有無虛函式,必然含有虛基類表指標。虛基類表中的內容為本類例項的偏移和基類例項的相對偏移值。如果有虛函式,那麼基類的虛函式表跟派生類的虛函式表是分開的。

在記憶體布局上,位址從低到高,順序如下:派生類的虛函式表指標+虛基類表指標+派生類的成員變數+「間隔」(4個位元組)+基類的虛函式表指標+基類的成員變數。派生類跟基類例項的位置關係跟普通繼承正好相反。

說明:「間隔」產生的原因是派生類重寫了基類的虛函式。如果沒重寫,則這一項沒有。"本類位址"指的是包含有虛基類的物件(或部分物件),也就是繼承鏈上的直接子類物件的位址,本例比較簡單,就是派生類物件位址。「本類位址跟虛基類表指標位址只差」,這個值經常是-4、0,-4表明「本類」還有乙個虛函式表指標;0則表明「本類」的第乙個4位元組儲存的就是虛基類表指標,沒有虛函式表指標。

圖 1 vs編譯器—單個虛擬繼承

(2)gnu的gcc編譯器:跟vs的編譯器類似,有不同的地方是,虛基類表跟派生類的虛函式表合併。另外通過虛基類表指標往正負兩個方向定址,可以獲得不同偏移值,也就是說有兩個功能一樣的虛函式表。不過在實際應用的時候,不知道虛基類表是否真的有用,測試了簡單的情況發現編譯器做了優化,根本就沒有用虛基類表來定址虛基類例項。

圖 2 gcc編譯器—單個虛擬繼承

2、 虛擬繼承多個基類

虛基類表要增加內容,有n個虛基類就有n項基類例項偏移值,再加上1項本類例項的偏移值,也就是n+1。

假設c虛擬繼承了a類和b類,考慮最複雜的情況(都有虛函式),那麼c類物件的記憶體布局如下

(vs編譯器):

c類虛函式表指標+虛基類表指標+c類成員變數+a類間隔(4個位元組) + a類虛函式表指標+ a類成員變數+ b類間隔(4個位元組)+b類虛函式表指標+ b類成員變數。

說明:當派生類重寫了該基類的虛函式,才會有「間隔」。「間隔」屬於虛函式被重新實現了的虛基類,可能是乙個標誌,也有可能是在函式呼叫的時候用上。不是很清楚。

圖 3 vs編譯器—虛擬繼承多個基類

(gcc編譯器):

c類虛函式表指標(包含虛基類表) + c類成員變數 + a類虛函式表指標 +  a類成員變數 + b類虛函式表指標 + b類成員變數。

相比較執行,使用gcc編譯器,派生類物件小一些。(圖略)

3、 虛擬繼承之菱形繼承

這裡的菱形繼承指的是:b、c虛擬繼承a,然後d普通繼承b、c。

d類的物件的記憶體布局如下

(vs編譯器)

b類虛函式表指標(該虛函式表包含d類獨有的虛函式的位址)+b類虛基類表指標+b類成員變數+c類虛函式表指標+c類虛基類表指標+c類成員變數+d類成員變數+「間隔」+a類虛函式表指標+a類成員變數。

說明:如果a類的虛函式沒有被重寫,那麼就沒有「間隔」。

圖 4 vs編譯器—菱形繼承

(gcc編譯器)

把b、c類的虛函式表跟虛基類表合併就是了。(圖略)

4、vs編譯器,「間隔」的疑問

「間隔」的問題,在沒有虛函式的情況下,重寫是沒有「間隔」的,所以覺得可能跟虛函式有關,也就是說是為了實現多型,具體是用在哪個地方,做了簡單的反彙編除錯(父類指標指向子類物件,呼叫被子類重寫了的虛函式),並沒有發現**用到了「間隔」,可能要在複雜的呼叫才會用上吧,目前搞不清楚。

5、虛基類表的問題

通過反彙編除錯發現在使用多型的時候,vs編譯器會去使用虛基類表,用於定址虛基類位址。而gcc編譯器則沒有這麼做,測試了比較簡單的情況,發現它做了優化,並沒有利用虛基類表,而是直接在派生類物件位址上加上乙個常數,獲得虛基類例項的位址。

關於C 中的虛擬繼承的一些總結

1.為什麼要引入虛擬繼承 虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如 類d繼承自類b1 b2,而類b1 b2都繼承自類a,因此在類d中兩次出現類a中的變數和函式。為了節省記憶體空間,可以將b1 b2對a的繼承定義為虛擬繼承,而a就成了虛擬基類。實現的 如下 class a ...

關於C 中的虛擬繼承的一些總結

關於c 中的虛擬繼承的一些總結 1.為什麼要引入虛擬繼承 虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如 類d繼承自類b1 b2,而類b1 b2都繼承自類a,因此在類d中兩次出現類a中的變數和函式。為了節省記憶體空間,可以將b1 b2對a的繼承定義為虛擬繼承,而a就成了虛擬基類...

關於C 中的虛擬繼承的一些總結

1.為什麼要引入虛擬繼承 虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。如 類d繼承自類b1 b2,而類b1 b2都繼承自類a,因此在類d中兩次出現類a中的變數和函式。為了節省記憶體空間,可以將b1 b2對a的繼承定義為虛擬繼承,而a就成了虛擬基類。實現的 如下 class a ...