c++和pyhon都是允许多继承的。
但由于被继承的多个父类可能直接或间接继承自一个基类,那么此时就有可能会产生一个结果,在此定义的class中包含多个同一基类的多个副本,这种情况在大多数时候是没有意义的,反而会在逻辑上造成二义性的错误。
举个栗子
class A{public:void func(){}};
class B: public A{};
class C: public A{};
class D: public B, public C{};
在上述的例子中,D间接通过B和C继承了A,那么此时D中就有了两个A的实例副本。
当然在上面的代码中是不存在语法错误的,因为不同副本中的func函数虽然定义相同,但是属于不同的作用域,所以不冲突,but...
//如果我一不小心....
D dd;
dd.func();
调用func函数的时候就会出现二义性错误,因为多继承的名字的查找是并行的。
//关于上述问题的解决方案,也是有许许多多的.
//1. 使用域作用符莱明确调用.
D dd; dd.C::func();
//2. 在子类中重写func.
class D:public B,public C{public: void func();};
//3. 在D中使用using引入基类中的func,明确指定.
class D:public B, public C{public: using B::func;};
//注意这里只能引入名字func而不是特定形参的func().
//其实本质上而言,这里引入其实就是一个声明。
//因为父类的作用域包含子类的作用域.
//这样一下引入, 就频闭了其他父类中的func.
//4. 在D中使用作用域符引入func明确指定.
class D:public B, public C{public: B::func;};
//在高版本c++语法中抛弃这一语法.
上面的例子中在公共基类中没有定义数据成员,如果定义了数据成员,那么,很多时候是冗余的,没有必要的,如果需要数据的多个副本,直接定义子对象即可。
此外,我们谈点正事儿,虚基类。
虚基类是什么?
继承时访问级别使用virtual关键字的基类被称为虚基类,这种方式成为虚继承。
class A{
public:
void func(){}
};
class B: virtual public A{
public:
void func(){}
};
//A为虚基类,上述就是虚继承.
虚基类的作用?
在多继承时,虚基类只有一个副本,可避免引起二义性错误。
#include<iostream>
class A{
public:
void func(){
std::cout<<"-----------A----------"<<std::endl;
}
};
class B: virtual public A{
public:
void f(){
}
};
class C: virtual public A{
};
class D: public C, public B{
};
int main(int argc, char** argv){
D d;
d.func();
return 0;
}
这里A拥有默认构造函数,所以一切正常。
那么,深入思考一下。
我们来考虑一个问题:
按照构造函数构造对象的顺序:先构造基类在构造子类,这样迭代推理,上面的D,首先将构造过程交给,B和C,同理,B和C又会同时去构造A,而在非虚继承中,A有两个副本,B,C各自构造一个,不冲突,但虚继承中,A只有一个副本,那B,C同时去构造,不久冲突了吗?
试想一下,在现实生活中,一群小孩分一块蛋糕,当发生冲突的时候,是否是会选择一个第三方的“公平”的 “仲裁者” 来决定蛋糕的划分?成年世界也同是。
所以,我们这里解决虚继承过程中的构造冲突的方案是:将虚基类的构造交给最终子类来在其构造函数的初始化列表中进行,如果没有在初始化列表中显式调用虚基类的构造函数,则默认调用虚基类的默认构造函数来初始化。如果虚基类莫得默认构造函数就出错。我在上面的例子中使用的就是默认构造函数初始化虚基类。
其次,应当记住,如果最终子类不是虚基类的直接子类,那么最终子类的中间父类对虚基类的初始化将被忽略。
#include<iostream>
class A{
public:
A(int a):a(a){}
void func(){
std::cout<<"a = "<<a<<std::endl;
std::cout<<"-----------A----------"<<std::endl;
}
private:
int a;
};
class B: virtual public A{
public:
B():A(20){}
void f(){
}
};
class C: virtual public A{
public:
C():A(30){}
};
class D: public C, public B{
public:
D():A(10){}
};
int main(int argc, char** argv){
D d;
d.func();
return 0;
}
//Results:
a = 10
-----------A----------
最后,虚基类会很大程度上降低c++的编译时的性能,慎用 。