我想知道「 虛擬基類 」是什麼以及它意味着什麼。 數組
讓我舉個例子: 函數
class Foo { public: void DoSomething() { /* ... */ } }; class Bar : public virtual Foo { public: void DoSpecific() { /* ... */ } };
做爲旁註,Dreaded Diamond的問題在於基類存在屢次。 所以,經過常規繼承,您相信您擁有: 佈局
A / \ B C \ / D
可是在內存佈局中,你有: this
A A | | B C \ / D
這解釋了爲何在調用D::foo()
,你有一個歧義問題。 可是當你想要使用A
的成員變量時, 真正的問題出現了。 例如,假設咱們有: spa
class A { public : foo() ; int m_iValue ; } ;
當你試圖從D
訪問m_iValue
時,編譯器會抗議,由於在層次結構中,它會看到兩個m_iValue
,而不是一個。 若是你修改一個,比方說, B::m_iValue
(那就是A::m_iValue
的父B
), C::m_iValue
不會被修改(也就是A::m_iValue
的母公司C
)。 code
這就是虛擬繼承的便利之處,就像它同樣,你將回到真正的鑽石佈局,不只有一個foo()
方法,並且只有一個m_iValue
。 對象
想像: 繼承
A
有一些基本功能。 B
增長了一些很酷的數據(例如) C
增長了一些很酷的功能,如觀察者模式(例如,在m_iValue
)。 D
繼承自B
和C
,所以來自A
使用正常繼承,從D
修改m_iValue
是不明確的,必須解決此問題。 即便它是, D
內有兩個m_iValues
,因此你最好記住它並同時更新這兩個。 接口
使用虛擬繼承,從D
修改m_iValue
是能夠的......可是......假設你有D
經過它的C
接口,您附加了一個觀察者。 經過它的B
接口,你能夠更新cool數組,它具備直接改變m_iValue
...... ip
因爲m_iValue
的更改是直接完成的(不使用虛擬訪問器方法),所以實現偵聽的代碼在C
,而且B
不知道它,所以不會調用經過C
「偵聽」的觀察者。 。
若是您的層次結構中有鑽石,則表示您有95%的人對所述層次結構作錯了。
除了關於多重和虛擬繼承的內容以外,還有一篇關於Dobb博士期刊的很是有趣的文章: 多重繼承被認爲是有用的
這意味着對虛擬功能的調用將被轉發到「正確」類。
C ++ FAQ Lite FTW。
簡而言之,它一般用於多繼承場景,其中造成「菱形」層次結構。 當您在該類中調用函數而且該函數須要被解析爲該底層類之上的類D1或D2時,虛擬繼承將打破底層中建立的歧義。 有關圖表和詳細信息,請參閱FAQ項目 。
它也用於姐妹表明團 ,這是一個強大的功能(雖然不適合膽小的人)。 請參閱此常見問題
另見有效C ++第3版(第2版中的43)中的第40項。
虛擬繼承中使用的虛擬基類是一種在使用多重繼承時防止出如今繼承層次結構中的給定類的多個「實例」的方法。
請考慮如下情形:
class A { public: void Foo() {} }; class B : public A {}; class C : public A {}; class D : public B, public C {};
上面的類層次結構致使「可怕的鑽石」,以下所示:
A / \ B C \ / D
D的實例將由B組成,其中包括A,而C也包括A.因此你有兩個「實例」(爲了更好的表達)A。
當您有這種狀況時,您可能會有歧義。 執行此操做時會發生什麼:
D d; d.Foo(); // is this B's Foo() or C's Foo() ??
虛擬繼承能夠解決這個問題。 當您在繼承類時指定虛擬時,您告訴編譯器您只須要一個實例。
class A { public: void Foo() {} }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
這意味着層次結構中只包含一個A「實例」。 因而
D d; d.Foo(); // no longer ambiguous
但願有助於做爲迷你總結。 有關更多信息,請閱讀本文和此內容 。 這裏也有一個很好的例子。
虛基類是沒法實例化的類:您沒法從中建立直接對象。
我認爲你混淆了兩件大相徑庭的事情。 虛擬繼承與抽象類不一樣。 虛擬繼承修改函數調用的行爲; 有時它會解析函數調用,不然這些函數調用將是模糊的,有時它會將函數調用處理推遲到非虛擬繼承中所指望的類。