一:虚基类的作用
当一个类的部分或者全部基类来自另一个共同的基类时,这些直接从一级共同基类继承来的成员就拥有相同的名称,这样就可能出现派生的二义性;且在派生类对象中,这些同名成员在内存中同时拥有多个拷贝,可以使用作用域分辨符来唯一标识并分别访问他们,也可以将共同基类设置成为虚基类,这时从不同路径继承过来的该类成员在内存中就只有一个拷贝,这样就解决了唯一标识问题。
二:虚基类的申明方式
virtual <继承方式> <基类名>
其中关键字 virtual 可以放在继承方式的后面。
三:使用虚基类时应该注意
1)一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
2)在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的对象。
3)虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。
4)最派生类是指在继承结构中建立对象时所指定的类。
5)在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
6)在虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中,都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
7)在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,基类的构造函数先于非虚基类的构造函数执行。
四:虚基类的构造函数
前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。
由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。
C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数 的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的对象只初始化一次。
C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。
下面举一例子说明具有虚基类的派生类的构造函数的用法。
#include <iostream.h>
class A
{
public:
A(const char *s) { cout<<s<<endl; }
~A() {}
};
class B : virtual public A
{
public:
B(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class C : virtual public A
{
public:
C(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class D : public B, public C
{
public:
D(const char *s1, const char *s2, const char *s3, const char *s4)
:B(s1, s2), C(s1, s3), A(s1)
{
cout<<s4<<endl;
}
};
void main()
{
D *ptr = new D("class A", "class B", "class C", "class D");
delete ptr;
}
该程序的输出结果为:
class A
class B
class C
class D
在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。
在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。
在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。
五:派生类对象的地址可以直接赋给虚基类指针,虚基类的引用可以直接引用派生类的对象
base *bptr=&obj;
base &ref=obj;
但返过来就不正确了