C 的虛基類

2021-06-25 21:10:44 字數 3801 閱讀 9818

虛基類

當在多條繼承路徑上有乙個公共的基類,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個例項(或多個副本),若只想儲存這個基類的乙個例項,可以將這個公共基類說明為

虛基類

虛基類.

在繼承中產生歧義的原因有可能基類是繼承類繼承了基類多次,從而產生了多個拷貝,即不止一次的通過多個路徑繼承類在記憶體中建立了基類成員的多份拷貝。虛的基本原則是在記憶體中只有基類成員的乙份拷貝。這樣,通過把基類繼承宣告為虛擬的,就只能繼承基類的乙份拷貝,從而消除歧義。用virtual限定符把基類繼承說明為虛擬的。

classx1:virtual public x

;classx2:virtual public x

;虛基類的初始化

虛基類的初始化與一般多繼承的初始化在語法上是一樣的,但建構函式的呼叫次序不同.

派生類建構函式的呼叫次序有三個原則:

(1)虛基類的建構函式在非虛基類之前呼叫;

(2)若同一層次中包含多個虛基類,這些虛基類的構造函

虛基類和非虛基類的區別

數按它們說明的次序呼叫;

(3)若虛基類由非虛基類派生而來,則仍先呼叫基類建構函式,再呼叫派生類的建構函式.

使用虛繼承

看下面這個例子

虛擬繼承在一般的應用中很少用到,所以也往往被忽視,這也主要是因為在c++中,多重繼承是不推薦的,也並不常用,而一旦離開了多重繼承,虛擬繼承就完全失去了存在的必要(因為這樣只會降低效率和占用更多的空間,關於這一點,我自己還沒有太多深刻的理解,有興趣的可以看網路上白楊的作品《rtti、虛函式和虛基類的開銷分析及使用指導》,說實話我目前還沒看得很明白,高人可以指點下我)。

以下面的乙個例子為例:

#include

#include

class ca

};class cb : public ca

;class cc : public ca

;class cd : public cb, public cc

;void main()

error c2385: 'cd::f' is ambiguous

即編譯器無法確定你在d.f()中要呼叫的函式f到底是哪乙個。這裡可能會讓人覺得有些奇怪,命名只定義了乙個ca::f,既然大家都派生自ca,那自然就是呼叫的ca::f,為什麼還無法確定呢?

這是因為編譯器在進行編譯的時候,需要確定子類的函式定義,如ca::f是確定的,那麼在編譯cb、cc時還需要在編譯器的語法樹中生成cb::f,cc::f等標識,那麼,在編譯cd的時候,由於cb、cc都有乙個函式f,此時,編譯器將試圖生成這兩個cd::f標識,顯然這時就要報錯了。(當我們不使用cd::f的時候,以上標識都不會生成,所以,如果去掉d.f()一句,程式將順利通過編譯)

要解決這個問題,有兩個方法:

1、過載函式f():此時由於我們明確定義了cd::f,編譯器檢查到cd::f()呼叫時就無需再像上面一樣去逐級生成cd::f標識了;

此時cd的元素結構如下:

|cb(ca)|

|cc(ca)|

故此時的sizeof(cd) = 8;(cb、cc各有乙個元素k)

2、使用虛擬繼承:虛擬繼承又稱作共享繼承,這種共享其實也是編譯期間實現的,當使用虛擬繼承時,上面的程式將變成下面的形式:

#include

#include

class ca

};class cb : virtual public ca //也有一種寫法是class cb : public virtual ca

;class cc : virtual public ca

;class cd : public cb, public cc

;void main()

此時,當編譯器確定d.f()呼叫的具體含義時,將生成如下的cd結構:

|cb|

|cc|

|ca|

同時,在cb、cc中都分別包含了乙個指向ca的虛基類指標列表vbptr(virtual base table pointer),其中記錄的是從cb、cc的元素到ca的元素之間的偏移量。此時,不會生成各子類的函式f標識,除非子類過載了該函式,從而達到「共享」的目的(這裡的具體記憶體布局,可以參看鑽石型繼承記憶體布局,在白楊的那篇文章中也有)。

也正因此,此時的sizeof(cd) = 12(兩個vbptr + sizoef(int));

另註:如果cb,cc中各定義乙個int型變數,則sizeof(cd)就變成20(兩個vbptr + 3個sizoef(int)

如果ca中新增乙個virtual void f1(){},sizeof(cd) = 16(兩個vbptr + sizoef(int)+vptr);

再新增virtual void f2(){},sizeof(cd) = 16不變。原因如下所示:帶有虛函式的類,其記憶體布局上包含乙個指向虛函式列表的指標(vptr),這跟有幾個虛函式無關。

在使用虛基類時要注意:

(1) 乙個類可以在乙個類族中既被用作虛基類,也被用作非虛基類。

(2) 在派生類的物件中,同名的虛基類只產生乙個虛基類子物件,而某個非虛基類產生各自的子物件。

(3) 虛基類子物件是由最派生類的建構函式通過呼叫虛基類的建構函式進行初始化的。

(4) 最派生類是指在繼承結構中建立物件時所指定的類。

(5) 派生類的建構函式的成員初始化列表中必須列出對虛基類建構函式的呼叫;如果未列出,則表示使用該虛基類的預設建構函式。

(6) 從虛基類直接或間接派生的派生類中的建構函式的成員初始化列表中都要列出對虛基類建構函式的呼叫。但只有用於建立物件的最派生類的構造函式呼叫虛基類的建構函式,而該派生類的所有基類中列出的對虛基類的建構函式的呼叫在執行中被忽略,從而保證對虛基類子物件只初始化一次。

(7) 在乙個成員初始化列表中同時出現對虛基類和非虛基類建構函式的呼叫時,虛基類的建構函式先於非虛基類的建構函式執行。

虛析構函式

虛析構函式是為了解決這樣的乙個問題:基類的指標指向派生類物件,並用基類的指標刪除派生類物件。

如果某個類不包含虛函式,那一般是表示它將不作為乙個基類來使用。當乙個類不準備作為基類使用時,使析構函式為虛一般是個壞主意。因為它會為類增加乙個虛函式表,使得物件的體積翻倍,還有可能降低其可移植性。

所以基本的一條是:無故的宣告虛析構函式和永遠不去宣告一樣是錯誤的。實際上,很多人這樣總結:當且僅當類裡包含至少乙個虛函式的時候才去宣告虛析構函式。

抽象類是準備被用做基類的,基類必須要有乙個虛析構函式,純虛函式會產生抽象類,所以方法很簡單:在想要成為抽象類的類裡宣告乙個純虛析構函式。

看下面的**例子

[cpp]view plain

copy

print?

#include

using

namespace

std;  

class

a    

virtual

~a()  

};  

class

c    

~c()  

};  

class

b:public

a    

~b()  

c c;  

};  

void

main()    

執行結果如下

如果把a的析構函式去掉virtual 關鍵字

執行結果如下

此時刪除基類的指標時候,實際上只呼叫了基類的析構函式,如果子類裡面有動態申請的空間,那麼物件就沒有刪除乾淨。

同樣的如果b的析構函式也宣告為虛析構函式,a的也是,執行結果和第乙個一樣

c 的類 虛基類 六

什麼是虛基類 就是他的派生類有兩個或者以上的派生類 通過虛繼承,這樣就可以避免派生類有多個基類的副本 從而減少記憶體消耗 關於繼承可以看我的這一篇新增鏈結描述 include include using namespace std class person class partymember vir...

關於C 虛基類

在繼承時,如果乙個派生類d,從幾個基類中繼承來,如f1,f2 假設有兩個類,也許有更多 而這些基類又繼承自同乙個基類m,那麼在d中就會有關於m的雙重拷貝,在d的物件中,如果使用成員或者函式,就要使用作用域識別符號來辨別該變數或者函式來自f1 or f2.虛基類使得d中關於m的雙重拷貝可以在d中只存放...

C 虛基類詳解

在上一節中,有兩個身份證號顯然是不合理的。為此,可以把class person這個共同基類設定為虛基類,這樣,從不同路徑繼承來的同名資料成員在記憶體中就只有乙個拷貝,同名函式也只有一種對映。虛基類 virtual base class 定義方式如下 class 派生類名 virtual 訪問限定符 ...