关于c++中多重继承该不该使用的争论不是我们关注的问题,但是现实世界很多模型确实是多重继承类别。和单一继承相比,它比较复杂,也不太好理解,而且多基派生可能会引起二义性。下面我们就说说多重继承可能的二义性和解决办法。
先看看没有共同基类的多基派生如何引起二义性。这种继承大致如图所示
这时候有个问题就是假如两个基类有同名的成员,编译器就无法知道要访问哪一个类的成员了,解决的方法就是加上作用域限定符,准确告诉编译器这个成员属于哪个类中的。用一个非常简单的例子代码演示下
- #include <iostream>
- using namespace std;
- class Base1 {
- public:
- int i;
- };
- class Base2 {
- public:
- int i;
- };
- class Derived : public Base1, public Base2 {
- };
- int main() {
- Derived d;
- //下面这样的用法就会产生错误,编译器不知道i到底属于哪个基类
- //d.i = 1;
- //加上类名限定符解决
- d.Base1::i = 1;
- d.Base2::i = 2;
- system("pause");
- return 0;
- }
类似于这种继承,由于Base1和Base2都继承Base,那Base中的成员会被拷贝到两个派生类中,这时候Derived又同时继承Base1和Base2,要是使用基类的成员,就是出现二义性,编译器无法准确知道成员到底属于谁。这种歧义的解决方法是继承的时候将继承自共同基类声明为virtual公有继承。这就是虚基类,这样引用中就只有一份基类的成员拷贝了。还是一个非常简单的例子
- #include <iostream>
- using namespace std;
- class Base {
- public:
- int i;
- };
- class Base1 : virtual public Base {
- public:
- int j;
- };
- class Base2 : virtual public Base {
- public:
- int k;
- };
- class Derived : public Base1, public Base2 {
- public:
- int sum;
- };
- int main() {
- Derived d;
- d.i = 1;
- d.j = 2;
- d.k = 3;
- d.sum = d.i + d.j + d.k;
- cout << d.sum << endl;
- system("pause");
- return 0;
- }
可以看出来,派生类Derived包含了Base1中的一个int 变量和Base1继承自Base中的一个int 变量,同样也包含了Base2中的一个int 变量和Base2继承自Base中的一个int 变量,所以Derived含有4个int变量,32位机器上正好是16个字节。
在看看使用虚基类的情况,看图吧
这种情况的对象内存布局大概是这样
Base1中应该含有一个指向Base对象4字节虚指针和自己的的一个int变量4字节,就是8字节,同样的原理Base2也应该是8字节,派生类Derived应该是20字节=两个基类的16字节+共同基类4字节。
多重继承要比单一继承复杂,可能会出现二义性。所以在Java语言中使用接口代替了多重继承。在c++中多重继承主要用于接口继承,接口继承仅仅是在一个派生类中加入了成员函数的声明,貌似Java就是这样的,但是c++并不支持这种做法,因为在c++中所有的继承都是实现继承,但是可以模拟实现,首先声明一个接口类,类中只有声明,没有成员数据和函数体,而且除了虚析构函数外,其余的函数都是纯虚函数,然后从这个接口类派生一个子类并实现基类中声明的函数体。关于实现继承、接口继承和可视继承会在以后有空写文章来介绍下。