在C++编程中,继承是一个核心概念,它允许通过扩展现有类来创建新类。然而,继承机制并不总是那么简单。特别是,当讨论虚拟继承时,必须非常小心。本文将探讨虚拟继承的工作原理,以及为什么在某些情况下应该避免使用它。
在没有虚拟继承的情况下,基类的成员会被分配为派生类的公共数据成员。这意味着派生类对象会包含两个独立的基类子对象。然而,当使用虚拟继承时,基类的对象只会在派生类对象中包含一次。这种机制有助于避免在多重继承中出现的菱形继承问题。
class Base { ... };
class X : public Base { ... };
class Y : public Base { ... };
class XY : public X, public Y { ... };
在上述代码中,如果没有使用虚拟继承,'Base'类的成员会在'XY'对象中被复制两次。但是,如果'X'和'Y'都使用虚拟继承'Base',那么'Base'只会在'XY'对象中被包含一次。
在虚拟继承中,最派生类('XY')负责初始化所有虚拟基类的子对象。这意味着'XY'的构造函数必须初始化'Base'子对象,并且不能允许'X'和'Y'的构造函数重新初始化它。
X::X(int A) : Base(A) {}
Y::Y(int A) : Base(A) {}
XY::XY() : X(3), Y(6) {}
在上面的代码中,'Base'构造函数的参数是隐式初始化的,而不是显式传递的。这意味着'Base'不会被调用,也不会接收任何参数。
虚拟基类还会引起赋值运算符的问题。编译器生成的赋值运算符可能会多次或一次分配虚拟基类的子对象。为了避免这个问题,需要在'X'和'Y'类中添加特殊的函数来防止'Base'类的成员被复制。
由于虚拟基类在内存中的分配方式,不能像处理普通基类那样进行类型转换。例如,不能将'Base'指针直接转换为'XY'指针。相反,必须使用'dynamic_cast'来执行这种转换。
许多作者建议应该避免使用虚拟继承,因为它会导致对象初始化和复制的问题。由于最派生类负责这些操作,它必须熟悉基类的内部结构。这会导致类之间的复杂依赖关系,从而使项目结构变得复杂,并在重构过程中需要对所有这些类进行额外的修订。所有这些都会导致新的错误,并使代码更难阅读。
类型转换的问题也可能是错误的来源。可以通过使用'dynamic_cast'来部分解决这些问题。但是,它太慢了,如果在代码中频繁使用它,这意味着项目架构可能非常糟糕。项目结构几乎总是可以在没有多重继承的情况下实现。毕竟,许多其他语言中没有这种机制,这并不妨碍程序员使用这些语言编写大型和复杂的项目。
不能完全拒绝虚拟继承:它有时可能是有用和方便的。但是,在创建大量复杂类之前,总是要三思。拥有许多小类和浅层继承的森林比处理几棵大树要好。例如,多重继承在大多数情况下可以通过对象组合来替代。
现在已经理解并同意对多重虚拟继承和多重继承本身的批评。但是,有没有使用它是安全和方便的情况呢?
是的,可以至少举出一个例子:混入(mix-ins)。如果不知道这是什么,请参考书籍《Enough Rope to Shoot Yourself in the Foot》。