大型程序的工具——多重繼承與虛繼承
一、多重繼承與虛繼承
1、多重繼承
在派生類的派生列表中可以包含多個基類,每個基類包含一個可選的訪問說明符。
多重繼承的派生列表也只能包含已經定義過的類,而且這些類不能是final的。對於派生類能夠繼承的基類個數,C++沒有進行特殊規定;但是在某個給定的派生列表中,同一個基類只能出現一次。
1)多重繼承的派生類從每個基類中繼承狀態
在多重繼承關系中,派生類的對象包含有每個基類的子對象。
2)派生類構造函數初始化所有基類
構造一個派生類的對象將同時構造並初始化它的所有基類子對象。與從一個基類進行的派生一樣,多重繼承的派生類的構造函數初始值也只能初始化它的直接基類。
派生類的構造函數將初始值列表將實參分別傳遞給每個直接基類。其中基類的構造順序與派生列表中基類的出現順序保持一致,而與派生類構造函數初始值列表中基類的順序無關
3)繼承的構造函數與多重繼承
在C++11新標準中,允許派生類從它的一個或幾個基類中繼承構造函數。但是如果從多個基類中繼承了相同的構造函數(即形參列表完全相同),則程序將產生錯誤。
如果一個類從它的多個基類中繼承了相同的構造函數,則這個類必須為該構造函數定義它自己的版本。
4)析構函數與多重繼承
派生類的析構函數只負責清除派生類本身分配的資源,派生類的成員及基類都是自動銷毀的。
析構函數的調用順序正好與構造函數相反。
5)多重繼承的派生類的拷貝與移動操作
與只有一個基類的繼承一樣,多重繼承的派生類如果定義了自己的拷貝/賦值構造函數和賦值運算符,則必須在完整的對象上執行拷貝、移動或賦值操作。只有當派生類使用的是合成版本的拷貝、移動或賦值成員時,才會自動對其基類分別執行這些操作。在合成的拷貝控制成員中,每個基類分別使用自己的對應成員隱式地完成構造、賦值或銷毀等工作。
2、類型轉換與多個基類
在只有一個基類的情況下,派生類的指針或引用能自動轉換成一個可訪問基類的指針或引用。多個基類的情況與之類似。
編譯器不會再派生類向基類的幾種轉換中進行選擇和比較,因為在它看來轉換到任意一種基類都一樣好。
1)基於指針類型或引用類型的查找
與只有一個基類的繼承一樣,對象、指針和引用的靜態類型決定了我們能夠使用哪些成員。
3、多重繼承下的類作用域
在只有一個基類的情況下,派生類的作用域嵌套在直接基類和間接基類的作用域中。查找過程沿著繼承體系自底向上進行,直到找到所需的名字。派生類的名字將隱藏基類的同名成員。
在多重繼承的情況下,相同的查找過程在所有直接基類中同時進行。如果名字在多個基類中都被找到,則對該名字的使用將具有二義性。
對於一個派生類來說,從它的幾個基類中分別繼承名字相同的成員是完全合法的,只不過在使用這個名字時必須明確指出它的版本。
4、虛繼承
在默認情況下,派生類中含有繼承鏈上每個類對應的子部分。如果某個類在派生過程中出現了多次,則派生類中將包含該類的多個子對象。
在C++語言中我們通過虛繼承的機制解決上述問題。虛繼承的目的是令某個類做出聲明,承諾願意共享它的基類。其中,共享的基類子對象稱為虛基類。在這種機制下,不論虛基類在繼承體系中出現了多少次,在派生類中都只包含唯一一個個共享的虛基類子對象。
1)使用虛基類
我們指定虛基類的方式是在派生列表中添加關鍵字virtual。virtual說明符表明了一種願望,即在後續的派生類當中共享虛基類的同一份實例 。如果某個類指定了虛基類,則該類的派生仍按常規方式進行。
1 #include <iostream> 2 #include <string> 3 4 class Base { 5 public: 6 Base() { 7 std::cout << __FUNCTION__ << std::endl; 8 } 9 }; 10 class D1 :public virtual Base { 11 public: 12 D1() :Base() { 13 std::cout << __FUNCTION__ << std::endl; 14 } 15 }; 16 class D2 :public virtual Base { 17 public: 18 D2() :Base() { 19 std::cout << __FUNCTION__ << std::endl; 20 } 21 }; 22 class D :public D1, public D2 { 23 public: 24 D() :D1(), D2() { 25 std::cout << __FUNCTION__ << std::endl; 26 } 27 }; 28 int main() 29 { 30 D d; 31 return 0; 32 }View Code
2)支持向基類的常規類型轉換
不論基類是不是虛基類,派生類對象都能被可訪問基類的指針或引用操作。
3)虛基類成員的可見性
5、構造函數與虛繼承
1)虛繼承的對象的構造方式
含有虛基類的對象的構造順序與一般的順序稍有區別:首先使用提供給最底層派生類構造函數的初始值初始化該對象的虛基類子部分,接下來按照直接基類在派生列表中出現的次序依次對其進行初始化。如果沒有顯式的初始化虛基類,則虛基類的默認構造函數被調用。
1 #include <iostream> 2 #include <string> 3 4 class Base { 5 public: 6 Base() { 7 std::cout << __FUNCTION__ << std::endl; 8 } 9 Base(int x):bx(x) { 10 std::cout << "hello Base" << std::endl; 11 } 12 private: 13 int bx; 14 }; 15 class D1 :public virtual Base { 16 public: 17 D1(int x) :Base(x), dx1(x) { 18 std::cout << __FUNCTION__ << std::endl; 19 } 20 private: 21 int dx1; 22 }; 23 class D2 :public virtual Base { 24 public: 25 D2(int x) :Base(x), dx2(x) { 26 std::cout << __FUNCTION__ << std::endl; 27 } 28 private: 29 int dx2; 30 }; 31 class D :public D1, public D2 { 32 public: 33 D(int x=0) :Base(x), D1(x), D2(x) { 34 std::cout << __FUNCTION__ << std::endl; 35 } 36 private: 37 int d; 38 }; 39 int main() 40 { 41 D d; 42 return 0; 43 }View Code
虛基類總是先於非虛基類構造,與它們在繼承體系中的次序和位置無關。
2)構造函數與析構函數的次序
一個類可以有多個虛基類。此時,這些虛的子對象按照它們在派生列表中出現的順序從左向右依次構造。
合成的拷貝和移動構造函數按照完全相同的順序執行,合成的賦值運算符中的成員也按照該順序賦值。和往常一樣,對象的銷毀順序與構造順序正好相反。
大型程序的工具——多重繼承與虛繼承