虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如下图所示。
类D继承自类B1、B2,而类B1、B2都继承自类A,因此出现如上图(b)所示的局面(非虚基类)。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。最后形成如上图(a)所示的情况。实现代码如下:
class A;//忽略C1和C2
class B1 : public virtual A; //注意关键字virtual
class B2 : public virtual A; //注意关键字virtual
class D : public B1,public B2;
一、为什么要引入虚拟继承
虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要。请看一个例子:
#include <iostream>
#include <memory.h>
class CA
{
int k; //如果基类没有数据成员,则在这里多重继承编译不会出现二义性
public:
void f() {cout << "CA::f" << endl;}
};
class CB : public CA{ }; //不是虚拟继承
class CC : public CA{ }; //不是虚拟继承
class CD : public CB, public CC{ };
void main()
{
CD d;
d.f();
}
程序运行出错:
error C2385: 对“f”的访问不明确
可能是“f”(位于基“CA”中)
也可能是“f”(位于基“CA”中)
即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。这里可能会让人觉得有些奇怪,命名只定义了一个CA::f,既然大家都派生自CA,那自然就是调用的CA::f,为什么还无法确定呢?
这是因为编译器在进行编译的时候,需要确定子类的函数定义,如CA::f是确定的,那么在编译CB、CC时还需要在编译器的语法树中生成CB::f,CC::f等标识,那么,在编译CD的时候,由于CB、CC都有一个函数f,此时,编译器将试图生成这两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译)
要解决这个问题,有两个方法:
第一个:重载函数f()。
class CD : public CB, public CC
{
public:
void f() {cout << "CD::f" << endl;}
};
第二个:使用虚拟继承。虚拟继承又称共享继承,这种共享其实也是在编译期间实现的,当使用虚拟继承时,上述程序变为:
#include <iostream>
#include <memory.h>
class CA
{
int k;
public:
void f() {cout << "CA::f" << endl;}
};
class CB : virtual public CA{ }; //也有一种写法是class CB : public virtual CA
//实际上这两种方法都可以
class CC : virtual public CA{ }; //使用了虚拟继承
class CD : public CB, public CC{ };
void main()
{
CD d;
d.f();
}
程序没有出错了。调用的f函数是CA::f()。
二、虚拟基类的初始化
虚拟基类的初始化是有最终派生类完成。而这个最终派生类是由每个特定类对象的声明来决定的。具体是什么意思呢?让我们来看一个具体的例子:
class ZooAnimal //虚拟基类
{
public:
ZooAnimal(string name,bool onExhibit,string fam_name) : _name(name),_onExhibit(onExhibit),_fam_name(fam_name)
{
}
virtual ~ZooAnimal() ;
virtual ostream& print(ostream&) const ;
string name() const {return _name ;}
string family_name() const {return _fam_name ;}
// ...
protected: //类的数据成员声明为protected
string _name ;
bool _onExhibit ;
string _fam_name ;
// ...
};
class Bear : public virtual ZooAnimal //虚拟继承
{
public:
enum DanceType{two_left_feet,macarena,fandango,waltz} ;
Bear(string name,bool onExhibit = true) : ZooAnimal(name,onExhibit,"Bear"),_dance(two_left_feet)
{
}
virtual ostream& print(ostream&) const ;
void dance(DanceType) ;
// ...
protected: //类的数据成员声明为protected
DanceType _dance ;
};
class Raccoon : public virtual ZooAnimal //虚拟继承
{
public:
Raccoon(string name,bool onExhibit = true) : ZooAnimal(name,onExhibit),_pettable(false)
{
}
virtual ostream& print(ostream&) const ;
bool pettable() const {return _pettable ;}
void pettable(bool petval){_pettable = petval ;}
// ...
protected: //类的数据成员声明为protected
bool _pettable ;
// ...
};
class Panda : public Bear,public Raccoon,public Endangered
{
public:
Panda(string name,bool onExhibit = true) ;
virtual ostream& print(ostream&) const ;
bool sleeping() const {return _sleeping;}
void sleeping(bool newval){ _sleeping = newval ;}
// ....
protected: //类的数据成员声明为protected
bool _sleeping ;
};
上面的派生是虚拟派生。在非虚拟派生中,派生类只能显示的初始化其直接基类。且,ZooAnimal的非虚拟派生中,Panda类不能在Panda成员初始化表中直接调用ZooAnimal的构造函数,因为ZooAnimal不是Panda的直接基类。然而,在虚拟派生中,只有Panda可以直接调用其ZooAnimal虚拟基类的构造函数。
那么,虚拟基类的初始化有其最终派生类来初始化,而这个最终派生类是由每个特定类对象的声明来决定的。例如:
我们在声明Bear类对象时:
Bear winnie("pooh") ; //声明Bear类对象,所以Bear是winnie对象的最终派生类
Bear是winnie对象的最终派生类,它所调用的ZooAnimal构造函数被执行。当我们写如下语句时:
cout << winnie.family_name() ;
输出的是:
The family name for pooh is Bear.
类似的,如下声明:
Raccoon meeko("meeko") ; //声明Raccoon类对象,所以Raccoon是meeko对象的最终派生类
声明Raccoon是meeko类对象的最终派生类,因此应执行Raccoon调用的ZooAnimal构造函数。当我们写下如下语句时:
cout << meeko.family_name() ;
输出的是:
The family name for meeko is Raccoon.
现在,当我们声明Panda对象时,比如:
Panda yolo("yolo") ; //声明Panda类对象,所以Panda是yolo对象的最终派生类
Panda是yolo类对象的最终派生类,所以初始化ZooAnimal称为Panda类的责任。
当一个Panda对象初始化时:1、在Raccoon和Bear的构造函数执行过程中,它们对于ZooAnimal构造函数的调用
不再执行。2、ZooAnimal构造函数被调用时,其实参是在Panda的初始化表中被指定。下面是Panda构造函数的具体实现:
Panda::Panda(string name,bool onExhibit = true)
: ZooAnimal(name,onExhibit,"Panda"),
Bear(name,onExhibit),
Raccoon(name,onExhibit),
Endangered(Endangered::E_DAO_XtrieveEnvironmentError,Endangered::critical)
_sleeping(false)
{
}
在Panda中,Bear和Raccoon类都被用作中间派生类而不是最终派生类。作为中间派生类,所有对虚拟基类构造函数
的调用都被自动抑制了。
上面的例子,很好的解释了虚拟基类的初始化变成了最终派生类的责任,这个最终派生类是由每个特定类对象的声明来决定的。特别是最后一句的理解。由特定类对象的声明来决定的,上面的程序很好的解释了这个意思。
详细具体的介绍可见:
http://baike.baidu.com/view/825838.htm