【编程语言】如何解决菱形继承问题

来源:互联网 发布:linux 查看压缩包目录 编辑:程序博客网 时间:2024/04/26 15:33

继承、封装和多态是面向程序设计(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)来解决菱形继承问题。这样做避免了菱形继承造成的二义性以及内存浪费问题,但增加了内存布局的复杂性,此外引入新的虚基类表又不可避免使用指针进行数据的存取,这将降低程序的执行效率。

原创粉丝点击