0x01 菱形繼承
假設有類B和類C,它們都繼承了相同的類A。另外還有類D,類D通過多重繼承機制繼承了類B和類C。
如果直接繼承會引發訪問不明確(二義性),以及數據冗余。如果直接指定訪問對象,可解決二義性,而要解決數據冗余,則要引入虛函數。
因為圖表的形狀類似於菱形(或者鑽石),因此這個問題被形象地稱為菱形問題(鑽石繼承問題)。
示例代碼:
#include <Windows.h> #include <iostream> using namespace std; class Life { public: Life() :LifeMeaning(5) { } public: int LifeMeaning; }; class Bird :public Life { public: Bird() :BirdMeaning(0x50) { } public: int BirdMeaning; }; class Human :public Life { public: Human() :HumanMeaning(0x100) { } public: int HumanMeaning; }; class Angel :public Bird, public Human { public: Angel() :AngelMeaning(0x30) { } public: int AngelMeaning; }; int main() { Angel Angel; return 0; }
內存窗口觀察Angel對象的基地址,可以看到有兩個05(Life中的成員變量LifeMeaning的值05),這是因為子類對象會包父類的成員變量。對於Bird和Human來說,都會去包含Life類中LifeMeaning的值05。對於天使Angel來說,會同時包含Bird和Human的所有成員。故而LifeMeaning的這個變量在子類Angel中出現了兩次,這是菱形繼承問題。
對於二義性,可以通過作用域符指定訪問對象來消除(Angel.Bird::LifeMeaning),而數據冗余的問題,則要通過虛繼承。
0x02 虛繼承
實例代碼:
#include <Windows.h> #include <iostream> using namespace std; class Life { public: Life() :LifeMeaning(0x5) { } public: int LifeMeaning; }; class LifePropagate1 :virtual public Life { public: LifePropagate1() :LifePropagate1Meaning(0x50) { } public: int LifePropagate1Meaning; }; class LifePropagate2 :virtual public Life { public: LifePropagate2() :m_B(0x60) { } public: int m_B; }; class NewLife :public LifePropagate1, public LifePropagate2 { public: NewLife() :NewLifeMeaning(0x100) { } public: int NewLifeMeaning; }; int main() { NewLife NewLifeObject; return 0; }
內存窗口觀察NewLifeObject對象的基地址:
LifePropagate1與LifePropagate2 共用一個虛基類。最終虛基類的數據只有一份,數據冗余和二義性問題不再。
那么,虛繼承又是怎么解決這些煩人的問題的呢?
可以看到在B和C中不再保存Life中的內容,保存了一份偏移地址,然后將最原始父類的數據保存在一個公共位置處這樣保證了數據冗余性的降低同時,也消除了二義性。