虚拟继承的秘密

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值