在C++中,类的继承层次可能会引发菱形问题(diamond problem),即一个基类被两个子类继承,然后创建另一个类从这两个子类继承。在这种情况下,继承类的对象将包含两个独立的基类副本。为了解决这个问题,C++实现了虚拟继承。
如果使用普通的多重继承:
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};
对象D将包含2个A的副本:一个通过B,另一个通过C。这导致对A::x的访问产生歧义,并且造成了不必要的内存浪费。
虚拟继承消除了基类的重复。基类A的一个实例将被所有子类使用:
class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
现在D只包含A的一个副本,访问A::x的歧义得以消除。
关键特点:
为什么不能通过作用域解析明确指定路径来解决菱形问题?
作用域解析只解决对基类成员的访问歧义,但并没有消除多余的基类副本。双重初始化和双重存储数据的问题依然存在。
在虚拟继承中,构造函数的调用顺序是什么?
虚拟基类的构造函数首先被调用,并且只调用一次,由继承层次中最后一个类的构造函数直接调用。
示例:
class A { public: A() { std::cout << "A "; } }; class B : public virtual A { public: B() { std::cout << "B "; } }; class C : public virtual A { public: C() { std::cout << "C "; } }; class D : public B, public C { public: D() { std::cout << "D "; } }; D d; // 输出:A B C D
在声明和定义类时,是否必须声明虚拟继承?
是的,在对相应基类进行继承时,必须在所有地方指定虚拟继承。否则将不可避免地发生编译错误,而多重继承将变成普通继承。
复杂的继承层次未应用虚拟继承,导致对象中出现了两个设置的副本:
优点:
缺点:
采用虚拟继承并一致使用最上层后代的构造函数:
优点:
缺点: