18.8 - 虚基类
Key Takeaway
在上一章 17.9 - 多重继承一课中,我们谈论了菱形继承问题。本章我们会继续该话题。
注意:本节是高级主题,可以作为选修。
菱形继承问题
下面是我们上一课中的例子(多了一些构造函数),说明菱形继承问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
也许你会认为上述代码会得到下面这样的继承结构:
如果要创建一个 Copier
类对象,默认情况下最终会得到PoweredDevice
类的两个副本——一个来自Printer
,一个来自Scanner
。它有以下结构:
我们可以创建一个简短的示例来演示这一点:
1 2 3 4 5 6 |
|
1 2 3 4 |
|
看到了吗?PoweredDevice
被构建了两次。
虽然这通常是期望的结果,但在有时你可能只想让 Scanner
和 Printer
共享 PoweredDevice
的一个副本。
虚基类
要共享基类,只需在派生类的继承列表中插入" virtual "关键字。这将创建所谓的虚基类,这意味着只有一个基类对象。基类对象在继承树中的所有对象之间共享,并且只构造一次。下面是一个示例(为了简单起见,没有构造函数),演示如何使用 virtual
关键字创建共享基类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
现在,当你创建一个 Copier
类对象时,每个Copier
将只获得一个由Scanner
和Printer
共享的PoweredDevice
副本。
但是,这又导致了另一个问题:如果Scanner
和Printer
共享一个PoweredDevice
基类,那么谁负责创建它呢?事实证明,答案是 Copier
。 Copier
的构造函数负责创建 PoweredDevice
。因此, Copier
可以直接调用非直接父类(non-immediate-parent构造函数一次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
再看前面的例子:
1 2 3 4 5 6 |
|
结果:
1 2 3 |
|
如您所见,PoweredDevice
只构造一次。
有几个容易忽视的细节:
- 首先,虚基类总是在非虚基类之前创建,这确保了所有基类都在它们的派生类之前创建。
- 其次,请注意
Scanner
和Printer
构造函数仍然有对PoweredDevice
构造函数的调用。在创建Copier
的实例时,这些构造函数调用将被忽略,因为Copier
负责创建PoweredDevice
,而不是Scanner
或Printer
。但是,如果我们要创建Scanner
或Printer
的实例,就会使用这些构造函数调用,并应用正常的继承规则。
第三,如果一个类继承了一个或多个具有虚父类的类,则最后被派生的类会负责构造虚基类。在本例中,Copier
继承了Printer
和Scanner
,它们都有一个PoweredDevice
虚基类。最最后被派生的类,负责PoweredDevice
的创建。注意,即使在单一继承的情况下也是这样:如果Copier
从Printer
单独继承,而Printer
从PoweredDevice
虚继承,那么Copier
仍然负责创建PoweredDevice
。
第四,所有继承虚基类的类都将有一个虚表,即使它们通常没有虚表,因此类的实例其大小会增加一个指针。
因为Scanner
和Printer
实际上是从PoweredDevice
派生的,所以Copier
只是一个PoweredDevice
子对象(subobject)。Scanner
和Printer
都需要知道如何找到单个PoweredDevice
子对象,这样它们才能访问它的成员(因为它们毕竟是从它派生的)。这通常是通过一些虚表操作来完成的(它实际上存储了从每个子类到PoweredDevice
子对象的偏移量)。