当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。
当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。
在继承中产生歧义的原因有可能是继承类继承了基类多次,如概述图所示,子类C最后会接受分别来自A和B的同一个或多个相同拷贝,从而产生了多个拷贝,即不止一次的通过多个路径继承类在内存中创建了基类成员的多份拷贝。而这些是A和B从父类继承而来,所以C类该继承A还是B传下来的还是都接受呢?这样就产生歧义,虚基类的基本原则是在内存中只有基类成员的一份拷贝。这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。用virtual限定符把基类继承说明为虚拟的。
当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。
在继承中产生歧义的原因有可能是继承类继承了基类多次,从而产生了多个拷贝,即不止一次的通过多个路径继承类在内存中创建了基类成员的多份拷贝。虚基类的基本原则是在内存中只有基类成员的一份拷贝。这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。用virtual限定符把基类继承说明为虚拟的。
class x1:virtual public x
{
//……
};
class x2:virtual public x
{
//……
};
(1)虚基类的构造函数在非虚基类之前调用;
(2)若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;
(3)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数。
在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:
class derive:virtual public base{};虚基类主要解决在多重继承时,基类可能被多次继承,虚基类主要提供一个基类给派生类,如:
#include <iostream>using namespace std;class B0// 声明为基类B0{ int nv;//默认为私有成员public://外部接口 B0(int n){ nv = n; cout << "Member of B0" << endl; }//B0类的构造函数 void fun(){ cout << "fun of B0" << endl; }};class B1 :virtual public B0{ int nv1;public: B1(int a) :B0(a){ cout << "Member of B1" << endl; }};class B2 :virtual public B0{ int nv2;public: B2(int a) :B0(a){ cout << "Member of B2" << endl; }};class D1 :public B1, public B2{ int nvd;public: D1(int a) :B0(a), B1(a), B2(a){ cout << "Member of D1" << endl; }// 此行的含义,参考下边的 “使用注意5” void fund(){ cout << "fun of D1" << endl; }};int main(void){ D1 d1(1); d1.fund(); d1.fun(); return 0;}执行结果:
Member of B0
Member of B1
Member of B2
Member of D1
fun of D1
fun of B0
这里D1在B1,B2上继承,间接继承B0,D1继承的成员变量有nv、nv1、nv2,并且只继承一次,若不是由虚基类继承而来,那么nv会被D1从B1和B2各继承一次,造成冗余。
(1) 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
(2) 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。
(3) 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
(4) 最远派生类是指在继承结构中建立对象时所指定的类。
(5) 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
(6) 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
(7) 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。