2017-04-24 32 views
12

考慮:爲什麼這個鑽石類的繼承輸出不是我所期望的?

#include <iostream> 

using namespace std; 

class A {// base class 
private: 
    int data; 
public: 
    A(int data = 0) 
    { 
     this->data = data; 
    } 
    void show() 
    { 
     cout << data << endl; 
     return; 
    } 
}; 

class B : virtual public A { 
public: 
    B(int data = 0) : 
     A(data) { 
    } 
}; 

class C : virtual public A { 
public: 
    C(int data = 0) : 
     A(data) { 
    } 
}; 

class D : public B, public C { 
public: 
    D(int dataB = 0, int dataC = 0) : 
     B(dataB), 
     C(dataC) { 
    } 
}; 

int main() { 
    D d(1, 2); 
    d.B::show(); 
    d.C::show(); 
    return 0; 
} 

上面的代碼是鑽石類的繼承圖。基類是A.我使用虛擬繼承來避免鑽石問題。但爲什麼這個程序的輸出是,0,0,而不是1,2正如我所料?

B的構造函數通過data=1,並且在其初始化程序列表中它調用AdataC的構造函數類似於data=2及其初始化列表,它調用Adata

然後我們要求BC子對象show它們的值。我們得到00不是12正如我所料。

+1

你可以非常容易地擁有1,2個 - 只是讓這個繼承不是虛擬的:)然後你將擁有兩個A的拷貝 –

+0

無論如何'this->'很奇怪。在現代C++中,你可以編寫'A(int data = 0):data(data){','構造函數初始化列表',它更乾淨,並且允許你擁有常量成員。 –

+1

這不是問題,但是你真的需要額外的東西,'std :: endl'嗎? ''\ n''結束一行。 –

回答

22

,當你用虛擬繼承這個方案,它是由在層次結構中最派生類(在這種情況下D)來調用公共基礎(A1,2的構造。

由於A的構造函數有一個默認參數data = 0,所以它可以用作默認構造函數。這就是發生了什麼事情,因爲你從D的成員初始化列表中刪除了它,所以常見的A子對象被默認構造。

如果刪除默認值data,你會得到強調一個不錯的編譯器錯誤:

A(int data) 
{ 
    this->data = data; 
} 

On g++ it results with

main.cpp: In constructor 'D::D(int, int)': 
main.cpp:37:16: error: no matching function for call to 'A::A()' 
     C(dataC) { 

請記住,虛擬繼承只有一個類型的子對象。並且BC子對象都引用它。您撥打show的電話打印不同的東西是不可能的,因爲他們訪問的數據是相同的。這就是爲什麼它取決於最派生的類,所以沒有歧義。

[class.mi/4]

甲基類說明不包含關鍵字虛擬指定非虛擬基類。包含關鍵字virtual的基類說明符指定了一個虛擬基類。對於大多數派生類的類網格中的非虛擬基類的每個不同的情況,最多派生的對象應包含該類型的相應的不同基類子對象。 對於每個指定爲虛擬的不同基類,最大派生對象應包含該類型的單個基類子對象。

[class.base.init/13.1]

在非委託構造函數,以下面的順序初始化前進:

  • 首先,和僅對最派生類的構造函數,虛擬基類按照它們出現在基類的有向無環圖的深度優先從左到右遍歷的順序被初始化爲,其中左到右」是在派生類基礎說明符列表的基類的出現的順序。

  • ...

所以,如果你想構建A具體數據您可以定義D::D()這樣,而不是:

D(int dataA = 0) : 
    A(dataA) { 
} 
7

當你有virtual繼承,virtual基類由大部分派生類的構造函數初始化。

D(int dataB = 0, int dataC = 0) : 
    B(dataB), 
    C(dataC) {} 

等同於:

D(int dataB = 0, int dataC = 0) : 
    A(), 
    B(dataB), 
    C(dataC) {} 

是,在你的情況下,同爲

D(int dataB = 0, int dataC = 0) : 
    A(0), 
    B(dataB), 
    C(dataC) {} 

除非你構建的B一個實例,

B(int data = 0) : 
    A(data) { 
} 

是一樣的小號

B(int data = 0) {} 

沒有代碼初始化A因爲AD構造已初始化。

同樣的事情適用於執行C::C(int data)

這解釋了您所看到的輸出。

0

我想你誤解了繼承和'鑽石問題'的概念virtual。繼承方式爲virtual,您創建了鑽石繼承模式,但是從您的帖子看來,您似乎希望避免使用該模式,而代之以兩個基地A,一個來自B,另一個來自C。要獲得這一點,只需避免BC中的虛擬繼承,當您的代碼將寫入1 2

順便說一句,如果僅僅B具有從AC傳統的繼承virtual繼承從A,然後D將有兩個基地A,但是從B再次默認初始化(如在其他答案解釋)。

相關問題