2013-01-22 194 views
6

我在調用類型層次結構中的構造函數規則時遇到了棘手的問題。這是我做的:構造函數調用層次結構

class A{ 
protected: 
    int _i; 
public: 
    A(){i = 0;} 
    A(int i) : _i(i){} 
    virtual ~A(){} 
    virtual void print(){std::cout<<i<<std::endl;} 
}; 

class B : virtual public A{ 
protected: 
    int _j; 
public: 
    B() : A(){_j = 0;} 
    B(int i, int j) : A(i), _j(j){} 
    virtual ~B(){} 
    virtual void print(){std::cout<<i<<", "<<j<<std::endl;} 
}; 

class C : virtual public B{ 
protected: 
    int _k; 
public: 
    C() : B(){_k = 0;} 
    C(int i, int j, int k} : B(i,j), _k(k){} 
    virtual ~C(){} 
    virtual void print(){std::cout<<i<<", "<<j<<", "<<k<<std::endl;} 
}; 

int main(){ 
    C* myC = new C(1,2,3); 
    myC->print(); 
    delete myC; 
    return 0; 
} 

現在,我想有新的C(1,2,3)調用B(1,2)的構造函數,然後又應該調用構造函數A(1 )來存儲_i = 1,_j = 2,_k = 3。當創建類C的實例myC時,出於某種原因,我不明白,但是,要調用的第一個構造函數是A的標準構造函數,即A :: A();這顯然會導致錯誤的結果,因爲受保護的變量_i被賦值爲0.構造函數A(1)永遠不會被調用。這是爲什麼?我覺得這非常直觀。是否有某種方法可以避免顯式調用類型層次結構中的所有構造函數以實現所需的行爲?

Thx求救!

+0

Thx爲答案。所以,我想我會回到Stroustrup重新閱讀虛擬繼承的概念。似乎默認情況下使用它並不明智;) – user1999920

+0

許多人想知道爲什麼默認情況下繼承不是虛擬的。那麼,你已經找到了自己的答案:) – Gorpik

回答

5

當你使用虛擬繼承,最派生類必須調用它的所有虛擬基地直接的構造函數。在這種情況下,C的構造函數必須調用BA的構造函數。由於您只調用B構造函數,因此它使用默認的A構造函數。 B構造函數調用另一個A構造函數並不重要:因爲它是一個虛擬基類,所以此調用將被忽略。

你必須解決這個問題有兩種方法:顯式調用構造函數A(int)

C(int i, int j, int k} : A (i), B(i,j), _k(k){} 

,或者使用的,而不是虛擬的正常繼承。

+1

大多數派生類沒有明確地爲它的虛擬基類顯式調用構造函數,它可以讓它們在問題中隱含地默認初始化。不過,它是這樣做的,儘管如此,它始終是最初派生虛擬基地的派生類。 –

+0

@CharlesBailey重讀我的答案,你是對的:我沒有正確解釋(*明確*是誤導)。我正在修復它。 – Gorpik

6

這是因爲您使用了虛擬繼承,這隻在存在多重繼承時纔有意義。只要繼承正常,並且一切都會如你所期望的那樣。

+0

如果'A'是一個接口,並且'B'擴展了這個接口,那麼虛擬繼承實際上是唯一正確的解決方案。 (當然,如果'A'是一個接口,它不會有任何數據成員,所以沒有用戶定義的構造函數。通常,虛擬繼承只適用於接口。) –

+0

@JamesKanze,這是錯的,你不如果您沒有從具有共同祖先的2+類進行多重繼承,則需要虛擬繼承。此外,您可以實現多個接口而無需虛擬繼承:請參閱COM。 – Steed

+0

我想我沒有得到你的「界面」的定義。你的意思是Java類型的接口(即沒有成員的抽象類)?然而,我在C++中看到很多不同的東西,可以調用itnerface,但是他們都不需要虛擬繼承。當然,在某些情況下抽象類可能會纏結在多個繼承圖中,但由於它們沒有成員,所以即使沒有虛擬繼承,vtable是否也能正常工作? –

7

你確實需要在這裏繼承一個virtual? 您遇到了問題,因爲第一個虛擬base ctor會首先被調用,但是當從B繼承C(後者已經有虛擬繼承的A,因此調用了默認值)時,您沒有指定任何值。

解決方案之一就是刪除虛擬繼承...正如Arne Mertz的答案中所述。 另一個(如果你真的想虛擬繼承)是從C構造函數顯式調用A

C(int i, int j, int k} : A(i), B(i,j), _k(k){} 
0

爲什麼要聲明虛擬繼承?如果您從類B中刪除虛擬關鍵字:virtual public A {...那麼您的代碼將正常工作。通過聲明虛擬A,C將直接調用A()。如果你刪除虛擬,那麼C將不會調用A()。