C++ 继承(四、多重继承)

多重继承

概念:C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
优缺点:

1、多重继承的优点很明显:简单,清晰,更有利于复用。不会因为基类一个小小的改变而大张旗鼓去改代码。

2、缺点:

1>二义性

      两个基类中有同名方法的时候,你不得不在子类的调用中指明此方法出自那个基类。这看起来有些麻烦,幸好在你迷糊的时候,编译器会提醒你。

2>钻石继承:在最终子类对象中存在不止一个公共基对象,可能造成数据不一致,通过虚拟继承可以解决钻石继承问题;

     假如类A派生了B和C,而B和C共同派生了D,麻烦就出现了。这种中间大两头小的继承树有个形象的名字:钻石型继承树(DOD:Diamond Of Death)。从名字看此君绝非善类,事实也如此,A是D的父类没错,但是有两条路径。这样的数据组织方式会有一些难以预料的后果。除去二义性不说,想想吧,D中有多少个看似重复的方法,有多少个名字相同的数据成员!

不惜一切代价,避免DOD的出现。除非,你认为DOD出现在这里是最恰当不过的,而且,确保你你使用了虚基类(虚继承),确保你对每个类的细节都完全清楚,确保你知道虚基类(虚继承)的副作用。

3>多重继承还会带来一些其他的问题:使用父类指针指向子类对象变成了一件复杂的事情。你不得不用到C++中提供的dynamic_cast来执行强制转换。至于dynamic_cast,也是个麻烦的家伙,它是在运行期间而非编译期间进行转换的(因为编译期间它不能确定到底要转向一个什么类型),因此除了会带来一些轻微的性能损失,它要求编译器允许RTTI(Runtime Type Information,运行时类型信息),也就是要求编译器保存所有类在运行时的信息。

多重继承还会使得子类的vtable变得不同寻常。单继承的vtable只是在父类vtable的表尾加上新的虚函数,子类对象的vtable中包含了有序的父类vtable。而对于多重继承,两个父类可能有完全不同的vtable,因此,子类的vtable中绝对不可能包含完整的有序的两个父类的vtable。子类的vtable中可能包含了两块不相连的父类vtable,因此每个父类都被迫追加了一个vtable,也就是,每个父类的对象都添加了一个指针。

孰优孰劣,自己把握。没有永远最好的,只有当前适合的。Java中摒弃了多重继承可能也是出于太过复杂,可能有不可料知的结果的原因。

不要随意使用多重继承。大多数的情况,用容器(也就是类的组合法)会更好些。

 

实例1:程序需要修改 m_n的值 通过虚拟继承可以实现

/*钻石继承*/
#include <iostream>
using namespace std;

class A{
public:
    A (int n=1000):m_n(n){}
    int m_n;
};
class B:/*virtual*/ public A{                                                                                                     public:
    B(int n):A(n){}
    void SetValue(int n){ m_n = n;} 
};
class C :/*virtual*/ public A{                                                                                                      public:
    C(int n):A(n){}
    int GetValue(void){return m_n;}
};
class D:public B,public C{
public:
    D(int n):B(2000),C(3000){}
};
int main(void)
{
    D d(100);
    d.SetValue(222);
    cout<<d.GetValue()<<endl;

    return 0;
}

要点:

1、从公共基类继承的子类都要使用 virtual
2、虚拟继承也需要对基类进行构造
3、虚拟继承可以解决钻石继承的问题

实例2:

/*继承中的指针*/
#include <iostream>
using namespace std;
class A{
public:
    A(int n):m_a(n){}
    void print(){
        cout<<"调用的是A中的print"<<endl;
    }
    int m_a;
};
class B{
public:
    B(int n):m_b(n){}
    virtual void print(){
        cout<<"调用的是B中的print"<<endl;
    }   
    int m_b;
};
class C: public B,public A{
public:
    C(int n):A(n),B(n){}
    void print(){
        cout<<"调用的是C中的print"<<endl;
    }   
};
int main(void)
{
    C c(100);
    A*pa = &c; 
    B*pb = &c; 
    pa->print();
    pb->print();    
        
    c.A::print();
    c.B::print();

    return 0;
}

结果:                                          解析:

调用的是A中的print        // 指向子类对象的基类指针,在无虚拟继承的情况下,最终调用的是基类中的函数
调用的是C中的print        // 指向子类对象的基类指针,在虚拟继承的条件下,最终调用的是子类中的函数
调用的是A中的print        // 继承的普通调用
调用的是B中的print


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值