多重继承往往会导致二义性:
class Base1
{
public:
void func()
{
cout<<"base1 func"<<endl;
}
};
class Base2
{
private:
bool func()
{
cout<<"base2 func"<<endl;
return true;
}
};
class Derived:public Base1,public Base2
{
};
int main()
{
Derived d;
//d.func();二义性
return 0;
}
也许你会奇怪,Base2中的func应该是不会被继承下来的,可是为什么在调用func时还会产生二义性呢?这是因为编译器在检查函数是否可取之前,先会判断这个函数是否是最佳匹配。此时,有两个最佳匹配,所以就会出现二义性。所以,最好是明确你要调用的是哪个基类的函数:d.Base1::func();
这还不是最可怕的情况,最可怕的是出现“钻石型多重继承”:B和C继承自A,而且D继承自B和C。此时,理论上讲,D中会有两份A的public成员(这里假定是public继承),但实际上,大多数时候,我们只希望有一份,此时只能通过虚继承来避免这种现象。
class Base
{
public:
void print()
{
cout<<"base"<<endl;
}
};
class Middle1:public Base
{
};
class Middle2:public Base
{
};
class Derived:public Middle1,public Middle2
{
};
此时,d.print();将会有二义性,因为他不知道自己调用的是从Middle1继承下来的print还是从Middle2继承下来的print。此时,只需要将Middle1和Middle2声明为虚继承,就会只从基类中保存一份print副本。
我们觉得,似乎所有的继承都该被声明为虚继承,但是虚继承会使派生类的大小变得更大,而且访问速度更慢。还有一点需要注意的,就是在使用虚继承之后,构造函数的调用顺序也变了:不再是派生类调用自己的基类,依次向上层调用了,而是从最高的基类开始,向下调用。
因此,不到万不得以,不要使用虚基类,在使用时,尽量避免在其中放置数据,因为它的初始化规则跟一般情况不同。
然后书中给出了一个使用的多重继承的例子:有一个接口类,我们需要实现它;恰巧我们有一个现成的类,这个类的功能跟我们要实现的类相似,但是我们需要对它的虚函数进行重写,已达到我们希望的功能,于是我们public继承了这接口,又private继承了它的实现,并对其中的某些实现重定义了。这时候,多重继承真的就派上了用场。
总的来说,多重继承可能会引起很多问题:二义性、构造、析构函数的顺序,类的大小、访问速度等等。但有时的确也会使用到它。