虚继承(virtual)的意义:在多继承中,保存共同父类类的多份同名成员,虽然有时是必要的,可以在不同的数据成员中分别存放不同的数据,但在大多数情况下,是我们不希望出现的。因为保留多份数据成员的拷贝,不仅占有较多的存储空间,还增加了访问的困难。为此,c++提供了,虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。
虚基类,需要设计和抽象,虚继承,是一种继承的扩展
虚继承语法: class 子类名: virtual 继承方式 父类
多个父类中有同名的成员,被继承到子类当中去后,会给访问带来很大的不方便,而且还浪费空间,需要解决多继承当中同名成员被继承后引起的访问混乱
#include "stdafx.h"
#include <iostream>
using namespace std;
class M
{
public:
M(int i) :data(i){}
int data;
};
class X:virtual public M
{
public:
X(int i):M(i){}
void setData(int i)
{
data = i;
}
};
class Y:virtual public M
{
public:
Y(int i) :M(i){}
int getData()
{
return data;
}
};
class Z :public X, public Y
{
public:
Z():X(2),Y(3),M(100){}
//这时只与M有关系 和前面的X Y都没关系
void dis()
{
//cout << X::data << endl;
//cout << Y::data << endl;
cout << data << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Z z;
z.dis();
z.setData(100000);
cout << z.getData() << endl;
z.dis();
return 0;
}
虚基类成员的可见性:
因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性,此外,如果虚基类的成员只被一条派生路径覆盖,则我们仍然可以直接访问这个被覆盖的成员,但是如果成员被多于一个基类覆盖,则一般情况下派生类必须为该成员自定义一个新的版本。
例如,假定B定义了一个名为x的成员,D1和D2都是从B虚继承得到的,D继承了D1和D2,则在D的作用域中,x通过D的俩个基类都是可见的,如果我们通过D的对象使用x,有三种可能性:
1.如果在D1和D2中都没有x的定义,则x将被解析为B的成员,此时不存在二义性,一个D的对象只含有x的一个实例
2.如果x是B的成员,同时是D1和D2中某一个的成员,则同样没有二义性,派生类的x比共享虚基类B的x优先级更高
3.如果在D1和D2中都有x的定义,则直接访问x将产生二义性问题
与非虚的多重继承体系一样,解决这种二义性问题最好的方法是在派生类中为成员自定义新的实例
#include "stdafx.h"
#include <iostream>
using namespace std;
class M
{
public:
int a=8;
};
class A :virtual public M
{
public:
int a=9;
};
class B :virtual public M
{
public:
int a=10;
};
class C : public A,public B
{
public:
void func();
int a = 11; //C必须定义自己版本的a,不然会产生错误
};
int _tmain(int argc, _TCHAR* argv[])
{
C c;
cout << c.a << endl;
return 0;
}
- 虚继承的对象的构造方式:
虚派生中,虚基类可以由最底层的派生类初始化,如果虚基类中没有默认构造函数,则它的派生类的构造函数就必须初始化它的虚基类
含有虚基类的对象的构造顺序与一般的顺序稍有区别:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类部分,接下来按照直接基类在派生列表中出现的次序依次对其进行初始化
例如创建一个Panda对象时:
1.首先使用Panda的构造函数初始值列表中提供的初始值构造虚基类ZooAnimal部分
2.接下来构造第一个直接基类Bear部分
3.然后构造第二个直接基类Raccoon部分
4.然后构造第三个直接基类Endangered
5.最后构造Panda部分
如果Panda没有显示地初始化ZooAnimal基类,则ZooAnimal的默认构造函数将被调用,如果ZooAnimal没有默认构造函数,则代码将发生错误,虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关,一个类如果有多个虚基类,则它们的构造顺序按照它们在派生列表中出现的顺序从左向右依次构造。
合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符中的成员也按照该顺序赋值,和往常一样,对象的销毁顺序与构造顺序正好相反