繼承的構造函數
1. 簡介:
子類為完成基類初始化,在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標準規定,繼承構造函數與類的一些默認函數(默認構造、析構、拷貝構造函數等)一樣,是隱式聲明,如果一個繼承構造函數不被相關代碼使用,編譯器不會為其產生真正的函數代碼。這樣比通過派生類構造函數“透傳構造函數參數”來完成基類初始化的方案,總是需要定義派生類的各種構造函數更加節省目標代碼空間。
?
2. 注意事項
(1)繼承構造函數無法初始化派生類數據成員。
繼承構造函數的功能是初始化基類,對于派生類數據成員的初始化則無能為力。解決的辦法主要有兩個:一是使用C++11特性就地初始化成員變量,可以通過=、{}對非靜態成員快速地就地初始化,以減少多個構造函數重復初始化變量的工作,注意初始化列表會覆蓋就地初始化操作。
class Derived :public Base
{
public:using Base::Base; //使用繼承構造函數 void display() { /* dosomethin */ } //假設派生類只是添加了一個普通的函數
private:double m_double{ 0.0 }; //派生類新增數據成員
};
?
(2)構造函數擁有默認值會產生多個構造函數版本,且繼承構造函數無法繼承基類構造函數的默認參數,所以我們在使用有默認參數構造函數的基類時就必須要小心。
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中的構造函數會有下面幾個版本:
1 2 3 4 |
|
那么B中對應的繼承構造函數將會包含如下幾個版本:?
1 2 3 4 |
|
可以看出,參數默認值會導致多個構造函數版本的產生,因此在使用時需格外小心。
?
(3)多繼承的情況下,繼承構造函數會出現“沖突”的情況,因為多個基類中的部分構造函數可能導致派生類中的繼承構造函數的函數名、參數(即函數簽名)相同。考察如下代碼:
class A {
public:A(int i) {}
};class B {
public:B(int i) {}
};class C : public A, public B {
public:using A::A;using B::B; //編譯出錯,重復定義C(int)C(int i) :A(i), B(i) {} //顯示定義繼承構造函數C(int)
};
為避免繼承構造函數沖突,可以通過顯示定義繼承類沖突的構造函數,組織隱式生成相應的繼承構造函數。
此外,使用繼承構造函數時,還需要注意以下幾點:
- 如果基類構造函數被申明為私有成員函數,或者派生類是從基類中虛繼承的 ,那么就不能在派生類中申明繼承構造函數;
- 一旦使用繼承構造函數,編譯器就不會再為派生類生成默認構造函數了。
?
3. 測試代碼:
#include <iostream>
using namespace std;class Base {
public:Base(int va) : m_value(va), m_c('0') {}Base(char c) : m_c(c), m_value(0) {}int m_value;char m_c;
};class Derived : public Base {
public:using Base::Base;void display() { cout << m_value << endl << m_c << endl; }
};int main()
{Derived d(5);d.display();return 0;
}
輸出結果:
?