繼承、封裝和多態是面向程序設計(OOP)的三大特點,而它們三者之中最具實際操作性的當屬繼承。通過繼承可以實現簡單功能的組合和定製,而多重繼承更將這種能力發揮到更高的境界。不過事事都有弊端,如果使用多重繼承不當很容易造成菱形繼承問題(diamond problem)。Bjarne Stroustrup用下面這個例子描述菱形繼承問題:
- class storable //this is the our base class inherited by transmitter and receiver classes
- {
- public:
- virtual void read();
- virtual void write();
- private:
- ....
- }
- class transmitter: public storable
- {
- public:
- void write();
- ...
- }
- class receiver: public storable
- {
- public:
- void read();
- ...
- }
- class radio: public transmitter, public receiver
- {
- public:
- void read();
- ....
- }
transmitter和receiver都從storable派生而來,而radio則從transmitter和receiver派生而來,是最終派生類。這樣的繼承關係很特殊,因爲從storable到radio存在兩條不同的路徑,即radio將從兩條不同的繼承路徑獲得storable的成員。如果不採取措施應對菱形繼承問題的話,編譯程序時將收到許多二義性的錯誤提示。如在上述示例中,如果radio類的對象調用write()方法,編譯器將無法確定應當調用transmitter中的write()方法或是receiver中的write()方法。解決的方法很簡單:只需在transmiiter和receiver類的繼承列表前添加virtual關鍵字即可,如下列代碼所示:
- class storable
- {
- public:
- int istorable;
- virtual void read()
- {
- cout<<"read() in storable called.\n";
- }
- virtual void write()
- {
- cout<<"write() int storable called.\n";
- }
- };
- // class transmitter
- class transmitter : virtual public storable
- {
- public:
- int itransmitter;
- void read()
- {
- cout<<"read() in transmitter called.\n";
- }
- };
- // class receiver
- class receiver : virtual public storable
- {
- public:
- int ireiceiver;
- void write()
- {
- cout<<"write() in receiver called.\n";
- }
- };
- // class radio
- class radio : public transmitter, public receiver
- {
- public:
- int iradio;
- };
爲了方便描述使用虛擬繼承後radio類對象的內存佈局,我在各類中都增加了一個整型的成員變量。
現在編寫主函數來打印radio類對象的內存佈局,代碼如下所示:
- typedef void (*FUN)();
- // main function
- int main()
- {
- radio r;
- r.istorable = 1;
- r.itransmitter = 2;
- r.ireiceiver = 3;
- r.iradio = 4;
- cout<<"address of r:"<<(INT_PTR*)&r<<endl;
- cout<<"vbtab of transmitter:\n";
- cout<<" [1] "<<((INT_PTR*)(*(INT_PTR*)&r))[0]<<endl;
- cout<<" [2] "<<((INT_PTR*)(*(INT_PTR*)&r))[1]<<endl;
- cout<<"itransmitter = "<<*((INT_PTR*)&r + 1)<<endl;
- cout<<"vbtab of receiver:\n";
- cout<<" [1] "<<((INT_PTR*)*((INT_PTR*)&r + 2))[0]<<endl;
- cout<<" [2] "<<((INT_PTR*)*((INT_PTR*)&r + 2))[1]<<endl;
- cout<<"ireiceiver = "<<*((INT_PTR*)&r + 3)<<endl;
- cout<<"iradio = "<<*((INT_PTR*)&r + 4)<<endl;
- cout<<"address of storable part:"<<(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<endl;
- cout<<"_vptr of storable:"<<(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<endl;
- cout<<" [1] "<<(INT_PTR*)*(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<" ";
- ((FUN)(*(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])))();
- cout<<" [2] "<<(INT_PTR*)*((INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1)<<" ";
- ((FUN)*((INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1))();
- cout<<"istorable = "<<*((INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1)<<endl;
- system("pause");
- return 0;
- }
運行結果如下所示:
- address of r:0021F73C
- vbtab of transmitter:
- [1] 0
- [2] 20
- itransmitter = 2
- vbtab of receiver:
- [1] 0
- [2] 12
- ireiceiver = 3
- iradio = 4
- address of storable part:0021F750
- _vptr of storable:002F790C
- [1] 002F12A8 read() in transmitter called.
- [2] 002F100F write() in receiver called.
- istorable = 1
- 請按任意鍵繼續. . .
從上面的結果不難看出,VC++編譯器引入虛基類表(virtual base table)來解決菱形繼承問題。這樣做避免了菱形繼承造成的二義性以及內存浪費問題,但增加了內存佈局的複雜性,此外引入新的虛基類表又不可避免使用指針進行數據的存取,這將降低程序的執行效率。