2011-11-16 82 views
5

我閱讀有關繼承的,我有一個重大的問題,我一直沒能解決幾個小時:虛擬繼承混亂

提供類Barvirtual函數的類,

class Bar 
{ 
    virtual void Cook(); 
}; 

之間有什麼不同:

class Foo : public Bar 
{ 
    virtual void Cook(); 
}; 

class Foo : public virtual Bar 
{ 
    virtual void Cook(); 
}; 

?谷歌搜索的時間和閱讀有很多關於它的用途的信息,但沒有人真正告訴我兩者之間的區別,只是讓我更加困惑。

+2

我不打算回答,因爲題材並不真的值得這麼淺薄的處理:但是沒有'virtual',從'Bar'繼承的每個類都會有它自己的'Bar'副本,'virtual'最派生的類將只有一個'Bar'的副本。 –

+0

嘗試:[此搜索](http://stackoverflow.com/search?q=q =虛擬+繼承+%5Bc%2B%2B%5D) –

+0

[在C++虛擬基類?](http://stackoverflow.com/questions/21558/in-c-virtual-base-class) –

回答

4

虛擬繼承只有在類繼承 Foo時纔有意義。如果我定義以下內容:

class B {}; 
class L : virtual public B {}; 
class R : virtual public B {}; 
class D : public L, public R {}; 

然後最終對象將只包含的B一個拷貝,由兩者 LR共享。如果沒有virtualD類型的對象將包含 B兩個副本,一個在L中,一個在R中。

有一些論點認爲,所有的繼承應該是虛擬的(因爲 在它有所作爲的情況下,那就是你最需要的時間)。然而,在實踐中,虛擬繼承是昂貴的,並且在大多數情況下是不必要的:在一個設計良好的系統中,大多數繼承只是從一個或多個「接口」繼承的具體類。這樣一個具體的類通常不會被設計成從本身派生的 ,所以沒有問題。但是有一些重要的例外:例如,如果您定義了接口,然後定義了接口的擴展,那麼擴展應該從基本接口幾乎繼承 ,因爲具體實現可能希望 實現多個擴展。或者,如果您正在設計mixin,其中某些類僅實現接口的一部分,並且最終的 類繼承了其中幾個類(每個接口的一部分都有一個)。最後,該criteron是否繼承幾乎 與否並不太難:

  • 如果繼承是不公開的,它可能不應該是虛擬 (我從來沒有見過一個例外),否則

  • 如果類沒有被設計成爲一個基類,沒有必要 虛擬繼承,否則

  • 繼承應該是虛擬的。

有一些例外,但上述規則在 安全方面犯錯誤;即使在虛擬繼承不是必需的情況下,通常也是「正確」的。

最後一點:虛擬基必須始終由最 派生類進行初始化,直接繼承(並聲明 繼承是虛擬的)的類。然而,在實踐中,這是一個沒有問題的問題。 如果您查看虛擬繼承有意義的情況,那麼始終是一個從接口繼承的情況,它將不包含 數據,因此只有(僅)默認構造函數。如果你發現自己 事實上繼承了帶有構造函數的類,而這些構造函數的參數爲​​ ,那麼現在是時候提出一些關於設計的嚴重問題了。

5

功能上明智的是兩個版本沒有太大的區別。在繼承的情況下,每個實現通常添加(vptr類似)指針(與virtual函數的情況相同)。這有助於避免由於多重繼承(在diamond inheritance問題)產生的多個基類副本

此外,virtual繼承代表調用基類的構造函數的權利。例如,

class Bar; 
class Foo : public virtual Bar 
class Other : public Foo // <--- one more level child class 

所以,現在Bar::Bar()將直接從Other::Other()調用,也將被放置在其他基類中的第一位置。

代表團功能在實現C++ 03 final class(在Java中)功能可以幫助:

class Final { 
    Final() {} 
    friend class LastClass; 
}; 

class LastClass : virtual Final { // <--- 'LastClass' is not derivable 
... 
}; 

class Child : public LastClass { // <--- not possible to have object of 'Child' 
}; 
3

在這種情況下,沒有什麼區別。虛擬繼承是通過派生類

struct A 
{ 
    int a; 
}; 

struct B : public virtual A 
{ 
    int b; 
} 

struct C : public virtual A 
{ 
    int c; 
}; 

struct D : public B, public C 
{ 
}; 

有成員變量a中的D實例單個副本與共享超子對象實例;如果A不是虛擬基類,則在D的實例中將有兩個A子對象。

+0

「_在這種情況下,沒有區別。」,直到你嘗試使用'static_cast'! – curiousguy

0

虛擬函數是一個函數,它可能在派生類中有不同的實現(雖然它不是必須的)。

在你的最後一個例子中是虛擬繼承。想象一下你有兩個從基類派生出來的類(A和B)(我們稱它爲'Base')。現在設想從A和B派生的第三個類C.如果沒有虛擬繼承,C將包含兩個「Base」副本。這可能會導致在編譯時模糊不清。虛擬繼承中的重要事情是必須在類C中提供'Base'類構造函數的參數(如果有),因爲來自A和B的調用將被忽略。

+0

「_這可能會導致模糊,而編譯._」這**會導致歧義_iff_你嘗試'C'是一個'基地'。 – curiousguy