轉載於:http://www.cnblogs.com/kaige/p/cplusplus_virtual_inheritance_derived_class_constructor.html
構造方法用來初始化類的對象,與父類的其它成員不同,它不能被子類繼承(子類可以繼承父類所有的成員變量和成員方法,但不繼承父類的構造方法)。因此,在創建子類對象時,為了初始化從父類繼承來的數據成員,系統需要調用其父類的構造方法。
如果沒有顯式的構造函數,編譯器會給一個默認的構造函數,並且該默認的構造函數僅僅在沒有顯式地聲明構造函數情況下創建。
構造原則如下:
1. 如果子類沒有定義構造方法,則調用父類的無參數的構造方法。
2. 如果子類定義了構造方法,不論是無參數還是帶參數,在創建子類的對象的時候,首先執行父類無參數的構造方法,然后執行自己的構造方法。
3. 在創建子類對象時候,如果子類的構造函數沒有顯示調用父類的構造函數,則會調用父類的默認無參構造函數。
4. 在創建子類對象時候,如果子類的構造函數沒有顯示調用父類的構造函數且父類自己提供了無參構造函數,則會調用父類自己的無參構造函數。
5. 在創建子類對象時候,如果子類的構造函數沒有顯示調用父類的構造函數且父類只定義了自己的有參構造函數,則會出錯(如果父類只有有參數的構造方法,則子類必須顯示調用此帶參構造方法)。
6. 如果子類調用父類帶參數的構造方法,需要用初始化父類成員對象的方式,比如:
1 #include <iostream.h> 2 3 class animal 4 { 5 public: 6 animal(int height, int weight) 7 { 8 cout<<"animal construct"<<endl; 9 } 10 … 11 }; 12 13 class fish:public animal 14 { 15 public: 16 fish():animal(400,300) 17 { 18 cout<<"fish construct"<<endl; 19 } 20 … 21 }; 22 void main() 23 { 24 fish fh; 25 }
在fish類的構造函數后,加一個冒號(:),然后加上父類的帶參數的構造函數。這樣,在子類的構造函數被調用時,系統就會去調用父類的帶參數的構造函數去構造對象。這種初始化方式,還常用來對類中的常量(const)成員進行初始化,如下面的代碼所示:
1 class point 2 { 3 public: 4 point():x(0),y(0) 5 private: 6 const int x; 7 const int y; 8 };
當然,類中普通的成員變量也可以采取此種方式進行初始化,然而,這就沒有必要了。
最近工作中某個軟件功能出現了退化,追查下來發現是一個類的成員變量沒有被正確的初始化。這個問題與C++存在虛繼承的情況下派生類構造函數的寫法有關。在此說明一下錯誤發生的原因,希望對更多的人有幫助。
我們代碼中存在虛繼承的類的繼承結構與下圖類似,並不是教科書中經典的菱形結構。從 Intermediate1 和 Intermediate3 到Base2 的繼承是虛繼承。Base1 和 Base2 包含一些成員變量,並提供了相應的構造函數接受指定的初始化值。Base2 還有一個缺省構造函數,把其成員變量都初始化為0。Intermediate1,2,3 也都提供了一個構造函數接受指定的初始化值,並在在初始化列表里調用Base1和Base2的構造函數完成初始化。

一位同事在做重構時,不小心把Final的代碼改成了:
1 class Final : public Intermediate2, public Intermediate3 { 2 public: 3 Final (int a, int b, int c) 4 : Intermediate2(a, b, c), 5 Intermediate3(b, c) 6 { 7 8 } 9 10 };
1 class Intermediate1 : public Base1, virtual public Base2 { 2 public: 3 Intermediate1(int a, int b, int c) 4 : Base1(a), 5 Base2(b, c) 6 { 7 8 } 9 }; 10 11 class Intermediate2 : public Intermediate1 { 12 public: 13 Intermediate2(int a, int b, int c) 14 : Intermediate1(a, b, c), 15 Base2(b, c) 16 { 17 18 } 19 }; 20 21 class Intermediate3 : virtual public Base2 { 22 public: 23 Intermediate3(int b, int c) 24 : Base2(b, c) 25 { 26 27 } 28 };
看上去,Final的構造函數將調用Intermediate2 和 Intermediate3的構造函數分別將m_a, m_b 和 m_c初始化成指定的值。可是,運行時發現m_b和m_c的值是0!明顯,這是調用了Base2的缺省構造函數。
原來,C++的規則是:如果在繼承鏈上存在虛繼承的基類,則最底層的子類要負責完成該虛基類部分成員的構造。我們可以顯式調用虛基類的構造函數完成初始化。如果不顯式調用虛基類的構造函數,則編譯器會調用虛基類的缺省構造函數。如果不顯式調用虛基類的構造函數,而虛基類沒有定義缺省構造函數,則會出現編譯錯誤。這條規則的原因是:如果不這樣做,則虛基類部分會在存在的多個繼承鏈條上被多次初始化。
很多時候,對於繼承鏈上的中間類,我們也會在其構造函數中顯式調用虛基類的構造函數,因為一旦有人要創建這些中間類的對象,我們也要保證它們得到正確的初始化。
所以,如果我們要把m_b和m_c初始化成指定的值,Final的構造函數的正確寫法應該是這樣:
1 Final (int a, int b, int c) 2 : Base2(b, c), 3 Intermediate2(a, b, c), 4 Intermediate3(b, c) 5 { 6 7 }
完整的測試程序如下所示,有興趣的同學可以自行編譯運行一下。也可以在調試器中單步運行Final的構造函數,看看前后兩種寫法分別是調用了Base2的哪個構造函數。
1 #include "stdafx.h" 2 #include <iostream> 3 4 using namespace std; 5 6 class Base1 { 7 public: 8 Base1(int a): m_a(a) {} 9 10 protected: 11 int m_a; 12 }; 13 14 class Base2 { 15 public: 16 Base2(int b, int c): m_b(b), m_c(c) {} 17 Base2() : m_b(0), m_c(0) {} 18 19 protected: 20 int m_b; 21 int m_c; 22 }; 23 24 class Intermediate1 : public Base1, virtual public Base2 { 25 public: 26 Intermediate1(int a, int b, int c) 27 : Base1(a), 28 Base2(b, c) 29 { 30 31 } 32 }; 33 34 class Intermediate2 : public Intermediate1 { 35 public: 36 Intermediate2(int a, int b, int c) 37 : Intermediate1(a, b, c), 38 Base2(b, c) 39 { 40 41 } 42 }; 43 44 class Intermediate3 : virtual public Base2 { 45 public: 46 Intermediate3(int b, int c) 47 : Base2(b, c) 48 { 49 50 } 51 }; 52 53 class Final : public Intermediate2, public Intermediate3 { 54 public: 55 Final (int a, int b, int c) 56 : Base2(b, c), 57 Intermediate2(a, b, c), 58 Intermediate3(b, c) 59 { 60 61 } 62 63 void Print() { 64 cout<<m_a<<", "<<m_b<<", "<<m_c<<endl; 65 } 66 }; 67 68 69 int _tmain(int argc, _TCHAR* argv[]) 70 { 71 Final finalObj(1, 2, 3); 72 finalObj.Print(); 73 74 return 0; 75 }
