【編程語言】如何解決菱形繼承問題

繼承、封裝和多態是面向程序設計(OOP)的三大特點,而它們三者之中最具實際操作性的當屬繼承。通過繼承可以實現簡單功能的組合和定製,而多重繼承更將這種能力發揮到更高的境界。不過事事都有弊端,如果使用多重繼承不當很容易造成菱形繼承問題(diamond problem)。Bjarne Stroustrup用下面這個例子描述菱形繼承問題:

  1. class storable //this is the our base class inherited by transmitter and receiver classes  
  2. {  
  3. public:  
  4.         virtual void read();  
  5.         virtual void write();   
  6. private:  
  7.         ....  
  8. }  
  9.   
  10. class transmitter: public storable   
  11. {  
  12. public:  
  13.         void write();  
  14.         ...  
  15. }   
  16.   
  17. class receiver: public storable  
  18. {  
  19. public:  
  20.         void read();  
  21.         ...  
  22. }  
  23.   
  24. class radio: public transmitter, public receiver  
  25. {  
  26. public:  
  27.         void read();  
  28.         ....  
  29. }  

transmitter和receiver都從storable派生而來,而radio則從transmitter和receiver派生而來,是最終派生類。這樣的繼承關係很特殊,因爲從storable到radio存在兩條不同的路徑,即radio將從兩條不同的繼承路徑獲得storable的成員。如果不採取措施應對菱形繼承問題的話,編譯程序時將收到許多二義性的錯誤提示。如在上述示例中,如果radio類的對象調用write()方法,編譯器將無法確定應當調用transmitter中的write()方法或是receiver中的write()方法。解決的方法很簡單:只需在transmiiter和receiver類的繼承列表前添加virtual關鍵字即可,如下列代碼所示:

  1. class storable  
  2. {  
  3. public:  
  4.     int istorable;  
  5.     virtual void read()  
  6.     {  
  7.         cout<<"read() in storable called.\n";  
  8.     }  
  9.     virtual void write()  
  10.     {  
  11.         cout<<"write() int storable called.\n";  
  12.     }  
  13. };  
  14. // class transmitter  
  15. class transmitter : virtual public storable  
  16. {  
  17. public:  
  18.     int itransmitter;  
  19.     void read()  
  20.     {  
  21.         cout<<"read() in transmitter called.\n";  
  22.     }  
  23. };  
  24. // class receiver  
  25. class receiver : virtual public storable  
  26. {  
  27. public:  
  28.     int ireiceiver;  
  29.     void write()  
  30.     {  
  31.         cout<<"write() in receiver called.\n";  
  32.     }  
  33. };  
  34. // class radio  
  35. class radio : public transmitter, public receiver  
  36. {  
  37. public:  
  38.     int iradio;  
  39. };  

爲了方便描述使用虛擬繼承後radio類對象的內存佈局,我在各類中都增加了一個整型的成員變量。

現在編寫主函數來打印radio類對象的內存佈局,代碼如下所示:

  1. typedef void (*FUN)();  
  2. // main function  
  3. int main()  
  4. {  
  5.     radio r;  
  6.     r.istorable = 1;  
  7.     r.itransmitter = 2;  
  8.     r.ireiceiver = 3;  
  9.     r.iradio = 4;  
  10.     cout<<"address of r:"<<(INT_PTR*)&r<<endl;  
  11.     cout<<"vbtab of transmitter:\n";  
  12.     cout<<"  [1] "<<((INT_PTR*)(*(INT_PTR*)&r))[0]<<endl;  
  13.     cout<<"  [2] "<<((INT_PTR*)(*(INT_PTR*)&r))[1]<<endl;  
  14.     cout<<"itransmitter = "<<*((INT_PTR*)&r + 1)<<endl;  
  15.     cout<<"vbtab of receiver:\n";  
  16.     cout<<"  [1] "<<((INT_PTR*)*((INT_PTR*)&r + 2))[0]<<endl;  
  17.     cout<<"  [2] "<<((INT_PTR*)*((INT_PTR*)&r + 2))[1]<<endl;  
  18.     cout<<"ireiceiver = "<<*((INT_PTR*)&r + 3)<<endl;  
  19.     cout<<"iradio = "<<*((INT_PTR*)&r + 4)<<endl;  
  20.     cout<<"address of storable part:"<<(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<endl;  
  21.     cout<<"_vptr of storable:"<<(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<endl;  
  22.     cout<<"  [1] "<<(INT_PTR*)*(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])<<"  ";  
  23.     ((FUN)(*(INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1])))();  
  24.     cout<<"  [2] "<<(INT_PTR*)*((INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1)<<"  ";  
  25.     ((FUN)*((INT_PTR*)*(INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1))();  
  26.     cout<<"istorable = "<<*((INT_PTR*)((char*)&r + ((INT_PTR*)(*(INT_PTR*)&r))[1]) + 1)<<endl;  
  27.     system("pause");  
  28.     return 0;  
  29. }  

運行結果如下所示:

  1. address of r:0021F73C  
  2. vbtab of transmitter:  
  3.   [1] 0  
  4.   [2] 20  
  5. itransmitter = 2  
  6. vbtab of receiver:  
  7.   [1] 0  
  8.   [2] 12  
  9. ireiceiver = 3  
  10. iradio = 4  
  11. address of storable part:0021F750  
  12. _vptr of storable:002F790C  
  13.   [1] 002F12A8  read() in transmitter called.  
  14.   [2] 002F100F  write() in receiver called.  
  15. istorable = 1  
  16. 請按任意鍵繼續. . .  

從上面的結果不難看出,VC++編譯器引入虛基類表(virtual base table)來解決菱形繼承問題。這樣做避免了菱形繼承造成的二義性以及內存浪費問題,但增加了內存佈局的複雜性,此外引入新的虛基類表又不可避免使用指針進行數據的存取,這將降低程序的執行效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章