子類為完成基類初始化,在C++11之前,需要在初始化列表調用基類的構造函數,從而完成構造函數的傳遞。如果基類擁有多個構造函數,那么子類也需要實現多個與基類構造函數對應的構造函數。
class Base { public: Base(int va) : m_value(va), m_c(‘0’) {} Base(char c) : m_c(c), m_value(0) {} private: int m_value; char m_c; }; class Derived : public Base { public: //初始化基類需要透傳基類的各個構造函數,那么這是很麻煩的 Derived(int va) : Base(va) {} Derived(char c) : Base(c) {} //假設派生類只是添加了一個普通的函數 void display() { //dosomething } };
書寫多個派生類構造函數只為傳遞參數完成基類的初始化,這種方式無疑給開發人員帶來麻煩,降低了編碼效率。從C++11開始,推出了繼承構造函數(Inheriting Constructor),使用using來聲明繼承基類的構造函數,我們可以這樣書寫。
class Base { public: Base(int va) : m_value(va), m_c('0') {} Base(char c) : m_c(c), m_value(0) {} private: int m_value; char m_c; }; class Derived : public Base { public: //使用繼承構造函數 using Base::Base; //假設派生類只是添加了一個普通的函數 void display() { /* dosomething */ } };
上面代碼中,我們通過using Base::Base把基類構造函數繼承到派生類中,不再需要書寫多個派生類構造函數來完成基類的初始化。更為巧妙的是,C++11標准規定,繼承構造函數與類的一些默認函數(默認構造、析構、拷貝構造函數等)一樣,是隱式聲明,如果一個繼承構造函數不被相關代碼使用,編譯器不會為其產生真正的函數代碼。這樣比通過派生類構造函數“透傳構造函數參數”來完成基類初始化的方案,總是需要定義派生類的各種構造函數更加節省目標代碼空間。
注意事項!!!
1.構造函數擁有默認值會產生多個構造函數版本,且繼承構造函數無法繼承基類構造函數的默認參數,所以我們在使用有默認參數構造函數的基類時就必須要小心。(即構造函數使用初始化列表是給類屬性賦值的,其中也可以自動默認值,如果帶了默認值的話,該類的構造函數就包含多個構造函數了,因為實現的時候回使用默認值替代缺少的)
例子:
class A { public: A(int a = 3, double b = 4) : m_a(a), m_b(b) {} void display() { cout << m_a << " " << m_b << endl; } } private: int m_a; double m_b; }; class B : public A { public: using A::A; };
那么A中的構造函數會有下面幾個版本:
A() A(int) A(int,double) A(constA&)
那么B中對應的繼承構造函數將會包含如下幾個版本://繼承構造函數無法繼承基類構造函數的參數,但是構造函數一個也不少
B() B(int) B(int,double) B(constB&)
2.多繼承的情況下,繼承構造函數會出現“沖突”的情況,因為多個基類中的部分構造函數可能導致派生類中的繼承構造函數的函數名、參數(即函數簽名)相同。考察如下代碼:
class A { public: A(int i) {} }; class B { public: B(int i) {} }; class C : public A, public B { public: using A::A; //兩個繼承構造函數都會定義一個C(int) using B::B; //編譯出錯,重復定義C(int) //顯示定義繼承構造函數C(int) C(int i) :A(i), B(i) {} };
為避免繼承構造函數沖突,可以通過顯示定義繼承類沖突的構造函數,組織隱式生成相應的繼承構造函數。(感覺還是老老實實的寫普通的繼承吧,不要寫繼承構造)