C 虛繼承實現原理(虛基類表指標與虛基類表)

2022-06-12 22:06:09 字數 4018 閱讀 9642

虛繼承和虛函式是完全無相關的兩個概念。

虛繼承是解決c++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這將存在兩個問題:其一,浪費儲存空間;第二,存在二義性問題,通常可以將派生類物件的位址賦值給基類物件,實現的具體方式是,將基類指標指向繼承類(繼承類有基類的拷貝)中的基類物件的位址,但是多重繼承可能存在乙個基類的多份拷貝,這就出現了二義性。

虛繼承可以解決多種繼承前面提到的兩個問題:

虛繼承底層實現原理與編譯器相關,一般通過虛基類指標和虛基類表實現,每個虛繼承的子類都有乙個虛基類指標(占用乙個指標的儲存空間,4位元組)和虛基類表(不占用類物件的儲存空間)(需要強調的是,虛基類依舊會在子類裡面存在拷貝,只是僅僅最多存在乙份而已,並不是不在子類裡面了);當虛繼承的子類被當做父類繼承時,虛基類指標也會被繼承。

實際上,vbptr指的是虛基類表指標(virtual base table pointer),該指標指向了乙個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移位址;通過偏移位址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持著公共基類(虛基類)的兩份同樣的拷貝,節省了儲存空間。

在這裡我們可以對比虛函式的實現原理:他們有相似之處,都利用了虛指標(均占用類的儲存空間)和虛表(均不占用類的儲存空間)。

虛基類依舊存在繼承類中,只占用儲存空間;虛函式不占用儲存空間。

虛基類表儲存的是虛基類相對直接繼承類的偏移;而虛函式表儲存的是虛函式位址。

此篇部落格有關於虛繼承詳細的記憶體分布情況

補充:1、d繼承了b,c也就繼承了兩個虛基類指標

2、虛基類表儲存的是,虛基類相對直接繼承類的偏移(d並非是虛基類的直接繼承類,b,c才是)

#include

using namespace std;

class a //大小為4

;class b :virtual public a //大小為12,變數a,b共8位元組,虛基類表指標4

;class c :virtual public a //與b一樣12

;class d :public b, public c //24,變數a,b,c,d共16,b的虛基類指標4,c的虛基類指標

;int main()

準備工作

1、vs2012使用命令列選項檢視物件的記憶體布局

微軟的visual studio提供給使用者顯示c++物件在記憶體中的布局的選項:/d1reportsingleclasslayout。使用方法很簡單,直接在[工具(t)]選項下找到「visual studio命令提示(c)」後點選即可。切換到cpp檔案所在目錄下輸入如下的命令即可

c1 [filename].cpp /d1reportsingleclasslayout[classname]

其中[filename].cpp就是我們想要檢視的class所在的cpp檔案,[classname]指我們想要檢視的class的類名。(下面舉例說明...)

2、檢視普通多繼承子類的記憶體布局

既然我們今天講的是虛基類和虛繼承,我們就先用上面介紹的命令提示工具檢視一下普通多繼承子類的記憶體布局,可以跟後文虛繼承子類的記憶體布局情況加以比較。

我們新建乙個名叫normalinheritance的cpp檔案,輸入一下內容。

/**

普通繼承(沒有使用虛基類)

*/

// 基類a

class a

; class b : public a

; class c : public a

; class d : public b, public c

; 上面是乙個簡單的多繼承例子,我們啟動visual studio命令提示功能,切換到normalinheritance.cpp檔案所在目錄,輸入一下命令:

c1  normalinheritance.cpp /d1reportsingleclasslayoutd

我們可以看到class d的記憶體布局如下:

從類d的記憶體布局可以看到a派生出b和c,b和c中分別包含a的成員。再由b和c派生出d,此時d包含了b和c的成員。這樣d中就總共出現了2個a成員。大家注意到左邊的幾個數字,這幾個數字表明了d中各成員在d中排列的起始位址,d中的五個成員變數(b::dataa、datab、c::dataa、datac、datad)各占用4個位元組,sizeof(d) = 20。

為了跟後文加以比較,我們再來看看b和c的記憶體布局:

虛繼承的記憶體分布情況

上面我們看到了普通多繼承子類的記憶體分布情況,下面我們進入主題,來看看典型的菱形虛繼承子類的記憶體分布情況。

我們新建乙個名叫virtualinheritance的cpp檔案,輸入一下內容:

/**

虛繼承(虛基類)

*/

#include

// 基類a

class a

; class b : virtual public a

; class c : virtual public a

; class d : public b, public c

; virtualinheritance.cpp和normalinheritance.cpp的不同點在與c和c繼承a時使用了virtual關鍵字,也就是虛繼承。同樣,我們看看b、c、d類的記憶體布局情況:

我們可以看到,菱形繼承體系中的子類在記憶體布局上和普通多繼承體系中的子類類有很大的不一樣。對於類b和c,sizeof的值變成了12,除了包含類a的成員變數dataa外還多了乙個指標vbptr,類d除了繼承b、c各自的成員變數datab、dataa和自己的成員變數外,還有兩個分別屬於b、c的指標。

那麼類d物件的記憶體布局就變成如下的樣子:

vbptr:繼承自父類b中的指標

int datab:繼承自父類b的成員變數

vbptr:繼承自父類c的指標

int datac:繼承自父類c的成員變數

int datad:d自己的成員變數

int a:繼承自父類a的成員變數

顯然,虛繼承之所以能夠實現在多重派生子類中只儲存乙份共有基類的拷貝,關鍵在於vbptr指標。那vbptr到底指的是什麼?又是如何實現虛繼承的呢?其實上面的類d記憶體布局圖中已經給出答案:

實際上,vbptr指的是虛基類表指標(virtual base table pointer),該指標指向了乙個虛表(virtual table),虛表中記錄了vbptr與本類的偏移位址;第二項是vbptr到共有基類元素之間的偏移量。在這個例子中,類b中的vbptr指向了虛表d::$vbtable@b@,虛表表明公共基類a的成員變數dataa距離類b開始處的位移為20,這樣就找到了成員變數dataa,而虛繼承也不用像普通多繼承那樣維持著公共基類的兩份同樣的拷貝,節省了儲存空間。

為了進一步確定上面的想法是否正確,我們可以寫乙個簡單的程式加以驗證:

int main()

得到結果為:

多重繼承 虛繼承與虛基類

一 多重繼承 單重繼承 乙個派生類最多只能有乙個基類 多重繼承 乙個派生類可以有多個基類 class 類名 繼承方式 基類1,繼承方式 基類2,派生類同時繼承多個基類的成員,更好的軟體重用 可能會有大量的二義性,多個基類中可能包含同名變數或函式 多重繼承中解決訪問歧義的方法 基類名 資料成員名 或成...

C 繼承與多型 三 虛基表與虛繼承

二.虛繼承 與 虛函式 完全不相關的兩個東西 三.總結 首先利用菱形繼承的案例 如下 說明如果派生類d中會出現兩個 a,乙個是b a,還有乙個是 c a,這樣便會有二義性,導致編譯器報錯。include using namespace std 菱形繼承案例 classa classb public ...

C 虛函式 虛繼承 虛基類 多型 智慧型指標

簡記如下 1,什麼是虛函式 基類中被virtual關鍵字修飾的成員函式 基類希望被派生類重新定義 而不是被繼承 普通繼承 的函式 作用 實現多型。通過繼承基類中的虛函式,在子類中過載實現不同操作 2,什麼是虛繼承 虛擬繼承 d繼承自b和c,b和c都繼承自a,這時可以將b和c對a的繼承定義為虛繼承。用...