1.虚基类
在多继承关系中,如果一个派生类的从两个父类那里继承过来,并且这两个父类又恰恰是从一个基类那里继承而来。那这样就麻烦了,因为你可能继承了两份一样的成员!这不仅多占用内存,而且还出现了所谓二义性问题。
#include <iostream> using namespace std; class Grandfather { public: int key; public: }; class Father1:public Grandfather { }; class Father2:public Grandfather { }; class Grandson:public Father1,public Father2 { };
int main() { Grandson A; //A.key=9; return 0; }
定义的四个类都很空,重点看继承关系即可。即Grandson类继承两个father类,会有两个key成员,这个时候如果试图使用这个key,注意已经声明为public类型,在主函数中试图赋值时候,会有“不唯一、模棱两可”的错误提示,即所谓的二义性问题发生。
因此,在多重继承中,若希望公共基类在派生类中只有一个拷贝,可将该基类说明为虚基类。即在定义派生类时,在继承的公共基类类名前加上关键字 virtual。那么这个时候,派生类和基类就只维护一份一个基类对象。避免多次拷贝,出现歧义。
class Father1:virtual public Grandfather
class Father2:virtual public Grandfather
2.注意事项
如果派生类继承了多个基类,基类中有虚基类和非虚基类,那么在创建该派生类的对象时,先调用虚基类的构造函数,然后调用非虚基类的构造函数,最后调用派生类的构造函数。若虚基类有多个,则虚基类构造函数的调用顺序取决于它们继承时的说明顺序。
class A { public: int x; void SetX(int a) { x = a; } A(int a = 0) { x = a; cout << "调用A构造函数" << endl; } void PA() { cout << "PA" <<x<< endl; } }; class B :virtual public A { public: int y; void SetY(int a) { y = a; } B(int a = 0, int b = 0):A(a) { y = b; cout << "调用B构造函数" << endl; } void PB() { cout << "PB" << "x="<<x<<"y="<<y<<endl; } }; class C :virtual public A { public: int z; void SetZ(int a) { z= a; } C(int a = 0, int b = 0) :A(a) { z = b; cout << "调用C构造函数" << endl; } void PC() { cout << "PC" <<"x="<<x<<"z="<<z<<endl; } }; class D :public B, C { int m; public: D(int a = 0, int b = 0, int c = 0,int d=0,int e=0):B(a,b),C(c,d) { m = e; cout << "调用D构造函数" << endl; } void PD() { PB();PC();cout<< "x=" << x << "y=" << y << "z=" << z<< endl; } };
int main()
{
D d1(1,2,3,4,5);
d1.PD();
return 0;
}
所以,当基类设置为虚基类时,只拷贝一次虚基类。
但是,为何基类的数据成员x数值为0???
因为类D的构造函数调用了B和C的构造函数,类B和C又调用了类A的构造函数,但是因为由于虚基类在D中只有一个拷贝,编译器无法确定是由B还是C的构造函数调用A的构造函数,所以,编译器约定,执行B和C的构造函数时不调用虚基类A的构造函数,而在类D的构造函数中直接调用A的默认的构造函数。
如果派生类继承了多个基类,基类中有虚基类和非虚基类,那么在创建该派生类的对象时,先调用虚基类的构造函数,然后调用非虚基类的构造函数,最后调用派生类的构造函数。若虚基类有多个,则虚基类构造函数的调用顺序取决于它们继承时的说明顺序。