1.多繼承
在本文中,我們解釋由gcc編譯器實現多繼承和虛繼承的物件的布局。雖然在理想的c++程式中不需要知道這些編譯器內部細節,但不幸的是多重繼承(特別是虛擬繼承)的實現方式有各種各樣的不太明確的結論(尤其是,關於向下轉型指標,使用指向指標的指標,還有虛擬基類的構造方法的呼叫命令)。 如果你了解多重繼承是如何實現的,你就能預見到這些結論並運用到你的**中。而且,如果你關心效能,理解虛擬繼承的開銷也是非常有用的。最後,這很有趣。
多重繼承
首先我們考慮乙個(非虛擬)多重繼承的相對簡單的例子。看看下面的c++類層次結構。
1 class top
2 ; class left : public top
4 ; class right : public top
6 ; class bottom : public left, public right
8 ;
使用uml圖,我們可以把這個層次結構表示為:
注意top被繼承了兩次(在eiffel語言中這被稱作重複繼承)。這意味著型別bottom的乙個例項bottom將有兩個叫做a的元素(分別為bottom.left::a和bottom.right::a)。
left、right和bottom在記憶體中是如何布局的?讓我們先看乙個簡單的例子。left和right擁有如下的結構:
left right
top::a top::a
left::b right::c
請注意第乙個屬性是從top繼承下來的。這意味著在下面兩條語句後
1 left* left =newleft();
2 top* top = left;
left和top指向了同一位址,我們可以把left object當成top object來使用(很明顯,right與此也類似)。那buttom呢?gcc的建議如下:
bottom
left::top::a
left::b
right::top::a
right::c
bottom::d
如果我們提公升bottom指標,會發生什麼事呢?
1 bottom* bottom =newbottom();
2 left* left = bottom;
這段**工作正常。我們可以把乙個bottom的物件當作乙個left物件來使用,因為兩個類的記憶體部局是一樣的。那麼,如果將其提公升為right呢?會發生什麼事?
1 right* right = bottom;
為了執行這條語句,我們需要判斷指標的值以便讓它指向bottom中對應的段。
bottom
left::top::a
left::b
right right::top::a
right::c
bottom::d
經過這一步,我們可以像操作正常right物件一樣使用right指標訪問bottom。雖然,bottom與right現在指向兩個不同的記憶體位址。出於完整性的緣故,思考一下執行下面這條語句時會出現什麼狀況。
1 top* top = bottom;
是的,什麼也沒有。這條語句是有歧義的:編譯器將會報錯。
1 error:top' is an ambiguous base of
bottom』
兩種方式可以避免這樣的歧義
1 top* topl = (left*) bottom;
2 top* topr = (right*) bottom;
執行這兩條語句後,topl和left會指向同樣的位址,topr和right也會指向同樣的位址。
2.問題:為什麼要有虛繼承?是為了解決什麼問題?
現象:假如我們有類a是父類,類b和類c繼承了類a,而類d既繼承類b又繼承類c(這種菱形繼承關係)。當我們例項化d的物件的時候,每個d的例項化物件中都有了兩份完全相同的a的資料。因為保留多份資料成員的拷貝,不僅占用較多的儲存空間,還增加了訪問這些成員時的困難,容易出錯,而實際上,我們並不需要有多份拷貝。
針對這種情況,c++提供虛基類(virtual base class)的方法,使得在繼承間接共同基類時只保留乙份成員。
現在,將上述類a申明為虛基類,方法如下:
class a //宣告基類a
; class b: virtual public a //宣告類b是類a的公用派生類,a是b的虛基類
; class c: virtual public a //宣告類c是類a的公用派生類,a是c的虛基類
; class d: public b, public c //類d中只有乙份a的資料
; [注意]:虛基類並不是在宣告基類時宣告的,而是在宣告派生類時,指定繼承方式時宣告的。因為乙個基類可以在生成乙個派生類時作為虛基類,而在生成另乙個派生類時不作為虛基類。
宣告虛基類的一般形式為:class 派生類名: virtual 繼承方式 基類名
即在宣告派生類時,將關鍵字 virtual 加到相應的繼承方式前面,經過這樣的宣告後,當基類通過多條派生路徑被乙個派生類繼承時,該派生類只繼承該基類一次,也就是說,基類成員只保留一次。
[注意]:為了保證虛基類在派生類中只繼承一次,應當在該基類的所有直接派生類中宣告為虛基類,否則仍然會出現對基類的多次繼承。比如:在上面的菱形繼承關係中,如果在派生類b和c中將類a宣告為虛基類,而在派生類d中沒有將類a宣告為虛基類,則如果再有乙個派生類e繼承自d,則在e中雖然從類b和c路徑派生的部分只保留乙份基類成員,但從類d路徑派生的部分還保留乙份基類成員。
問題:如何對虛基類進行初始化呢?
如果在虛基類中定義了帶引數的建構函式,而且沒有定義預設建構函式,則在其所有派生類(包括直接派生或間接派生的派生類)中,通過建構函式的初始化列表對虛基類進行初始化。如下:
class a //宣告基類a
; class b: virtual public a //a是b的虛基類
//b類建構函式,在初始化列表中對虛基類a進行初始化
}; class c: virtual public a //a是c的虛基類
//c類建構函式,在初始化列表中對虛基類a進行初始化
}; class d: public b, public c
//d類建構函式,在初始化列表中對所有基類進行初始化
};[注意]:在定義類d的建構函式時,與以往使用的方法有所不同。以往,在派生類的建構函式中只需負責對其直接基類初始化,再由其直接基類負責對間接基類初始化。現在,由於虛基類在派生類中只有乙份資料成員,所以這份資料成員的初始化必須由派生類直接給出。如果不由最後的派生類直接對虛基類初始化,而由虛基類的直接派生類(如類b和類c)對虛基類初始化,就有可能由於在類b和類c的建構函式中對虛基類給出不同的初始化引數而產生矛盾。所以規定:在最後的派生類中不僅要負責對其直接基類進行初始化,還要負責對虛基類初始化。
有的讀者會提出:類d的建構函式通過初始化表調了虛基類的建構函式a,而類b和類c的建構函式也通過初始化表呼叫了虛基類的建構函式a,這樣虛基類的建構函式豈非被呼叫了3次?大家不必過慮,c++編譯系統只執行最後的派生類對虛基類的建構函式的呼叫,而忽略虛基類的其他派生類(如類b和類c) 對虛基類的建構函式的呼叫,這就保證了虛基類的資料成員不會被多次初始化。
C 多繼承 虛繼承
一,多繼承 include include using namespace std class b1 繼承類c void main 主函式 c類按照順序繼承b2,b1,b4,b3 再按照資料成員定義順序 memberb1,memberb4,memberb3,memberb2 最後是自己的構造器 二,...
繼承(單繼承 多繼承 菱形繼承 虛繼承)
一 單繼承 單繼承是一種 乙個子類只有乙個直接父類 的繼承關係。二 多繼承 多繼承是一種 乙個子類有兩個或兩個以上直接父類 的繼承關係。三 菱形繼承 菱形繼承由兩個 或以上 單繼承,乙個多繼承構成,結構如下 顯然,上例中assistant類多繼承了student和teacher兩個類,而studen...
C 多繼承 菱形繼承 虛繼承
b和c都單繼承了a d繼承了b和c 是多繼承 有兩個或兩個以上的基類就是多繼承 class a public int ma class b public a public int mb class c public a public int mc class d public b,public c ...