虚拟基类

由于一个类可能多次成为派生类的间接基类,因此 C++ 提供了一种优化这种基类的工作方式的方法。 虚拟基类提供了一种节省空间和避免使用多重继承的类层次结构中出现多义性的方法。

每个非虚拟对象包含在基类中定义的数据成员的一个副本。 这种重复浪费了空间,并要求您在每次访问基类成员时都必须指定所需的基类成员的副本。

当将某个基类指定为虚拟基时,该基类可以多次作为间接基而无需复制其数据成员。 基类的数据成员的单个副本由将其用作虚拟基的所有基类共享。

当声明虚拟基类时,virtual 关键字将显示在派生类的基列表中。

请考虑下图中的类层次结构,它演示了模拟的午餐排队。

模拟午餐排队图

模拟的午餐排队图

在该图中,Queue 是 CashierQueue 和 LunchQueue 的基类。 但是,当将这两个类组合成 LunchCashierQueue 时,会出现以下问题:新类包含类型 Queue 的两个子对象,一个来自 CashierQueue,另一个来自 LunchQueue。 下图显示了概念上的内存布局(实际物理内存布局可能会进行优化)。

模拟午餐排队对象

模拟的午餐排队对象

请注意,LunchCashierQueue 对象中有两个 Queue 子对象。 以下代码将 Queue 声明为虚拟基类:

// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};

virtual 关键字可确保只包含子对象 Queue 的一个副本(请参阅下图)。

使用虚拟基类模拟午餐排队对象

模拟午餐排队对象和虚拟基类

一个类可以同时具有一个给定类型的虚拟组件和非虚拟组件。 下图演示了这种情况。

同一个类的虚拟组件与非虚拟组件

类的虚拟组件与非虚拟组件

在图中,CashierQueue 和 LunchQueue 将 Queue 用作虚拟基类。 但是,TakeoutQueue 将 Queue 指定为基类而不是虚拟基类。 因此,LunchTakeoutCashierQueue 具有类型 Queue 的两个子对象:一个来自包含 LunchCashierQueue 的继承路径,另一个来自包含 TakeoutQueue 的路径。 下图对此进行了演示。

带虚拟和非虚拟继承的对象布局

对象布局中的虚拟继承与非虚拟继承

备注

与非虚拟继承相比较,虚拟继承提供了显著的大小优势。但是,它可能会引入额外的处理开销。

如果派生类重写它从虚拟基类继承的虚函数,并且派生基类的构造函数或析构函数使用指向虚拟基类的指针调用该虚函数,则编译器可能会将其他隐藏的“vtordisp”字段引入到具有虚拟基的类中。 /vd0 编译器选项将禁止添加隐藏的 vtordisp 构造函数/析构函数置换成员。 默认的 /vd1 编译器选项会在必要时启用它们。 仅当确定所有类构造函数和析构函数以虚拟方式调用虚函数时才关闭 vtordisps。

/vd 编译器选项会影响整个编译模块。 使用 vtordisp 杂注可以逐个类地禁用 vtordisp 字段,然后重新启用这些字段:

#pragma vtordisp( off )
class GetReal : virtual public { ... };
#pragma vtordisp( on )

请参见

参考

多个基类