2017-07-09 75 views
2

我有以下程序:爲什麼C++不允許派生類在初始化列表中使用基類成員?

#include<iostream> 
using namespace std; 
struct Base01{ 
    int m; 
    Base01():m(2){} 
    void p(){cout<<m<<endl;} 
}; 
struct Derived01:public Base01{ 
    Derived01():m(3){} 
}; 
struct Derived02:virtual public Base01{ 
    Derived01():m(4){} 
}; 
struct my: Derived01,Derived02{ 
    my():m(5){} 
}; 
int main(){ 
    return 0; 
} 

兩個GCC /鐺報道編譯錯誤。

我只想知道這裏的語言設計考慮什麼,爲什麼派生類只能在初始化列表中調用基類ctor,但不能直接使用基類成員?

回答

8

你在構造函數初始化列表中做的是初始化。在對象的整個生命週期中,必須完成一次。在一般情況下,這是啓動對象使用期限

基類的構造函數(在派生類的構造函數正確變爲活動狀態之前完成了工作)已經初始化了基類的所有直接子對象。它已經開始了他們的一生。如果試圖從派生類的構造函數中調入並初始化一個基類的直接子對象,顯然這將是同一對象的第二次初始化。這在C++中是完全不可接受的。語言設計通常不允許您第二次初始化某些內容。

在你的情況下,子對象的基本類型爲int,所以很難看到這種「重新初始化」的危害。但考慮一些不重要的東西,比如std::string對象。你如何建議派生類應該「撤消和重做」已由基類執行的初始化?雖然形式上可以正確執行,但構造函數初始值設定項列表不適用於此目的。

在一般的情況下做類似的東西需要一個語言功能將允許用戶告訴基類的構造沿「線的東西請留下你的這個子對象初始化,我才能夠達到,並從後面將其初始化派生類「。但是,C++不會爲用戶提供這種功能。虛擬基類初始化中存在一個模糊的類似特徵,但它提供了一個非常特定的(和不同的)目的。

+1

排序中的一個錯誤 - 派生的構造函數可以控制基類子對象構造函數的參數,如果在派生構造函數得到控制之前調用基構造函數,這顯然是不可能的。談論施工責任可能會更好。 (當然,無論先發生什麼,雙重初始化仍然是個問題) –

+0

@ BenVoigt好點。我的意思是說,基類是首先構造的,並且只有在派生類構造函數中發生「其他所有事情」之後纔會構造基類。但是,當然,如果語言允許用戶在派生contsructor初始值設定項列表中引用基類成員,那麼排序規則將會不同(很有可能)。無論排序如何,避免雙重初始化的問題仍然存在。 – AnT

3

在C++中這樣做的正確方法是將該值傳遞給基類構造函數。您的Base01類需要一個額外的構造函數,它爲m指定所需的值。事情是這樣的:

struct Base01{ 
    int m; 
    Base01():m(2){} 

    // Added this: 
    Base01(int mVal) : m(mVal) {} 

    void p(){cout<<m<<endl;} 
}; 

struct Derived01:public Base01{ 
    Derived01() : Base01(3) {} // Calling base constructor rather than 
           // initializing base member 
}; 

struct Derived02:virtual public Base01{ 
    Derived01() : Base01(4){} // Same here 
}; 

struct my: Derived01,Derived02{ 
    my(): Base01(5){}   // And here. 
}; 

螞蟻說,你不能初始化兩次 - 但是你可以設置它使事情都是由如上做初始化你首先想要的方式。

2

你當然可以使用基類成員在構造函數初始化程序列表:

struct Base 
{ 
    int x; 
    Base(int x) : x(x) {} 
}; 

struct Derived 
{ 
    int y; 
    Derived() : Base(7), y(x) {} 
} 

這裏,基座部件x出現在初始化的派生成員y;它的價值將被使用。

AnT在解釋爲什麼ctor-initializer列表不能用於(重新)初始化基本子對象的成員方面做了很好的工作。

0

這裏的基礎語言設計考慮因素是分離關注點(避免使基類依賴於其派生類),並且基類負責初始化其自己的成員(以及它的任何基礎)。

相關的考慮是在基類構造函數完成之前,基類的成員不存在 - 就派生類構造函數而言。如果一個派生類的初始化器列表能夠達到並初始化基類成員,那麼有兩種可能的後果

  • 如果基類的構造函數沒有被調用,當得到的將不存在其成員班級嘗試初始化它們。
  • 如果基類構造函數已被調用,則該成員已被初始化。初始化(與賦予重新初始化不同)在對象的生命週期中發生一次,因此再次初始化它是沒有意義的。

這些可能性在實踐中都沒有實際意義,除非基類設計得不好(例如,它的構造函數沒有正確初始化其成員)。所需的機器類型可能是有意義的(例如,改變層次結構中基類構造的順序,取決於派生類嘗試初始化的成員)會使編譯器更加複雜(例如,能夠控制和跟蹤基類成員的構建順序,以防派生類選擇進入),也意味着構造類的順序將取決於派生類)。這會在派生類中引入基類行爲(它被初始化的方式)的依賴關係。

更簡單的方法是讓基類提供一個構造函數,用於正確初始化有問題的成員,並讓派生類構造函數在其初始化列表中調用基類構造函數。所有上述(假設)考慮因素都會消失。

相關問題