二、虚继承
1.概念
默认情况下,C++的派生列表中不允许同一个基类出现两次,但是,如果两个基类都继承了同一个类A,那么两个基类派生出的子类就会包含两次类A的部分
为了解决上述问题,C++中就出现了虚继承,通过虚继承,无论虚基类在整个继承链中出现了多少次,子类中都只出现一次。
示例
class animal
{
public:
animal(){cout<<__func__<<endl;}
~animal(){cout<<__func__<<endl;}
};
class bear:public virtual animal
{
public:
bear(){cout<<__func__<<endl;}
~bear(){cout<<__func__<<endl;}
};
class cat:public virtual animal
{
public:
cat(){cout<<__func__<<endl;}
~cat(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public cat, public bear, public endangered
{
public:
panda(){cout<<__func__<<endl;}
~panda(){cout<<__func__<<endl;}
};
int main(int argc, char const *argv[])
{
panda t;
return 0;
}
上述代码就是个虚继承的例子,输出结果如下
通过输出结果显示,animal只创建了一次,如果将派生列表的virtual关键字去掉,输出结果如下
所以,上述代码通过虚继承,让基类animal只在子类中出现了一次
和单一继承与多重继承一样,虚继承的的子类对象可以切割转化成对应的基类,可以通过基类的指针或者引用指向子类对象
见博客https://blog.csdn.net/Master_Cui/article/details/110119371 https://blog.csdn.net/Master_Cui/article/details/109899018 https://blog.csdn.net/Master_Cui/article/details/109849186
3.虚继承的问题
与非虚多重继承一样,如果在多个不同的基类中定义了同名的成员,会出现二义性问题,解决办法依然是指定作用于或者在子类中也定义同名的成员,见博客https://blog.csdn.net/Master_Cui/article/details/110119371
4.构造函数与虚继承
在单一继承和非虚多重继承的情况下,创建一个子类对象时,各个类的构造的顺序是按照派生列表中类出现的顺序来调用的。
但是虚继承不一样,在虚继承的体系中,创建一个子类对象时,虚基类总是先于非虚基类构造。
class a
{
public:
a(){cout<<__func__<<endl;}
~a(){cout<<__func__<<endl;}
};
class b :virtual public a
{
public:
b(){cout<<__func__<<endl;}
~b(){cout<<__func__<<endl;}
};
class c
{
public:
c(){cout<<__func__<<endl;}
~c(){cout<<__func__<<endl;}
};
class d
{
public:
d(){cout<<__func__<<endl;}
~d(){cout<<__func__<<endl;}
};
class e:public c, public b, virtual public d
{
public:
e(){cout<<__func__<<endl;}
~e(){cout<<__func__<<endl;}
};
当一个e的对象被创建时,输出结果是这样的
过程如下:首先查看派生列表。发现有虚基类,第一个发现的虚基类时a,所以先创建a部分,调用a的构造函数,然后发现了虚基类d,于是就创建d并调用d的构造函数,虚基类都创建完成了,开始创建非虚基类,非虚基类在派生列表中的出现顺序分别是c,b,所以创建并调用了c和b的构造函数,最后创建e并调用e的构造函数
析构顺序和构造的顺序正好相反
如果一个虚基类既是基类也是子类,在创建虚基类的时候,依然会创建虚基类的基类
示例
class a
{
public:
a(){cout<<__func__<<endl;}
~a(){cout<<__func__<<endl;}
};
class b : public a
{
public:
b(){cout<<__func__<<endl;}
~b(){cout<<__func__<<endl;}
};
class c:public virtual b
{
public:
c(){cout<<__func__<<endl;}
~c(){cout<<__func__<<endl;}
};
class d:public virtual b
{
public:
d(){cout<<__func__<<endl;}
~d(){cout<<__func__<<endl;}
};
class e:public c, public d
{
public:
e(){cout<<__func__<<endl;}
~e(){cout<<__func__<<endl;}
};
class f:public e, public a
{
public:
f(){cout<<__func__<<endl;}
~f(){cout<<__func__<<endl;}
};
代码的输出结果如下
上述代码中,先出现了一条警告,这是因为a在f的对象中存在两份,当访问a中的元素时,无法确定到时是访问哪个a中的元素,编译器分不出来,但是只要不访问a中的元素编译器就不会报错
因为在f的派生类列表中先发现的是虚基类b,而虚基类b又是a的子类,所以先创建a,然后创建b,之后按照非虚基类在派生列表中出现的顺序分别创建cde,接着在派生列表中又发现了一个a,于是又创建了一个a,最后创建f,析构函数的调用顺序仍然和构造函数相反
所以,当一个基类在子类对象中创建两份时,访问出现两份的基类成员时会出现二义性问题,因为编译器无法区分到底要访问哪个基类中的对应的成员
比如在a中添加一个成员at,访问时,就会出现如下错误
提示有二义性
多重继承也有这个问题
所以在实际应用中,虚继承和多重继承用的很少,单一继承用的较多
参考
《C++ Primer》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出