虚基类是用关键字virtual声明继承的父类,即便该基类在多条链路上被一个子类继承,但是该子类中只包含一个该虚基类的备份,虚基类主要用来解决继承中的二义性问题,这就是是虚基类的作用所在。
正是由于虚基类的这个作用,所以在每个子类的构造函数中必须显示的调用该虚基类的构造函数,不管该虚基类是不是直接的父类。
其次,虚基类的构造函数的调用早于其他非虚基类的构造函数的调用。
上面两点就是虚基类的特性。
例如:
class CBase { };
class CDerive1:virtual public CBase{ };
class CDerive2:virtual public CBase{ };
class CDerive12:public CDerive1,CDerive2{ };
则在类CDerive12的对象中,仅有类CBase的一个对象数据
#include <iostream>
using namespace std;
class CBase
{
public:
int a;
public:
CBase(int na)
{
a=na;
cout<<"CBase constructor! "<<endl;
}
~CBase(){cout<<"CBase deconstructor! "<<endl;}
};
//派生类1(声明CBase为虚基类)
class CDerive1:virtual public CBase
{
public:
CDerive1(int na):CBase(na)
{
cout<<"CDerive1 constructor! "<<endl;
}
~CDerive1(){cout<<"CDerive1 deconstructor! "<<endl;}
int GetA(){return a;}
};
//派生类2(声明CBase为虚基类)
class CDerive2:virtual public CBase
{
public:
CDerive2(int na):CBase(na)
{
cout<<"CDerive2 constructor! "<<endl;
}
~CDerive2(){cout<<"CDerive2 deconstructor! "<<endl;}
int GetA(){return a;}
};
//子派生类
class CDerive12:public CDerive1,public CDerive2
{
public:
CDerive12(int na1,int na2,int na3):CDerive1(na1),CDerive2(na2),CBase(na3)
{
cout<<"CDerive12 constructor! "<<endl;
}
~CDerive12(){cout<<"CDerive12 deconstructor! "<<endl;}
};
void main()
{
CDerive12 obj(100,200,300);
cout<<" CDerive12:a = "<<obj.a<<endl;
//得到从CDerive1继承的值
cout<<" from CDerive1 : a = "<<obj.CDerive1::GetA()<<endl;
//得到从CDerive2继承的值
cout<<" from CDerive2 : a = "<<obj.CDerive2::GetA()<<endl;
}
CDerive12 obj对象的值如下:
输出结果:
由此可以看出,CDerive12中的int a不是来自CDerive1或CDerive2,而是来自一份从CBase直接过来的拷贝。由此可知,其公共基类的构造函数只调用了一次,并且优先于非基类的构造函数调用;并且发现,子派生类的对象obj的成员变量的值只有一个,所以,当公共基类CBase被声明为虚基类后,虽然它成为CDerive1和CDerive2的公共基类,但子派生类CDerive12中也只有它的一个备份(一份从CBase直接过来的拷贝)。
内存模型
例如:
class A
{
public:
int a;
};
class B : virtual public A
{
public:
int b;
};
class C :virtual public A
{
public:
int c;
};
class D : public B, public C
{
public:
int d;
};
上面这种菱形的继承体系中,如果没有virtual继承,那么D中就有两个A的成员int a;继承下来,使用的时候,就会有很多二义性。而加了virtual继承,在D中就只有A的成员int a;的一份拷贝,该拷贝不是来自B,也不是来自C,而是一份单独的拷贝。
继承体系:
内存模型为:
所以,结果如下:sizeof(A) = 4; sizeof(B) = 12; sizeof(C) = 12; sizeof(D) = 24。
sizeof(B) = 12是因为类B除了继承A的数据成员外,还多了一个指向虚基类的指针。类C同理
sizeof(D) = 24是因为类D除了继承B和C的数据成员外,还多了两个分别来自B、C的指向虚基类的指针
对于虚基类的几个注意点:
(1)如果在虚基类中定义了带有参数的构造函数,且没有定义默认形式的构造函数,则在整个继承过程中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用;
(2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的,该派生类的其他基类对虚基类的调用构造函数则被忽略;
(3)若在同一层次中同时包含虚基类和非虚基类,那么先调用虚基类的构造函数,在调用非虚基类的构造函数,最后调用派生类的构造函数,析构则相反;
(4)对于多个虚基类,则构造函数执行顺序从左到右;
(5)对于多个非虚基类来说,构造函数的执行顺序也是如此;
(6)若虚基类由非虚基类派生而来,那么仍然先调用基类构造函数,再调用派生类的构造函数;
其他链接:
http://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/10/2133843.html
http://my.oschina.net/chen0dgax/blog/156160