11

我有钻石类的层次结构:

    A
  /   \
 B     C
  \   /
    D

为了避免 A 在 D 中的两个副本,我们需要在 B 和 C 处使用虚拟继承。

class A                     {  }; 
class B: virtual public A {};
class C: virtual public A   { }; 
class D: public B, public C { }; 

问题:为什么虚拟继承需要在 B 和 C 执行,即使歧义在 D?如果它在 D 处会更直观。

为什么标准委员会会这样设计这个功能?如果 B 和 C 类来自 3rd 方库,我们该怎么办?

编辑:我的回答是指示 B 和 C 类,它们不应在创建派生对象时调用 A 的构造函数,因为它将由 D 调用。

4

5 回答 5

8

我不确定他们选择以这种方式设计虚拟继承的确切原因,但我相信原因与对象布局有关。

假设 C++ 以解决菱形问题的方式设计,您将虚拟继承 D 中的 B 和 C,而不是虚拟继承 B 和 C 中的 A。现在,B 和 C 的对象布局是什么?好吧,如果没有人试图从他们那里虚拟继承,那么他们每个人都会拥有自己的 A 副本,并且可以使用标准的、优化的布局,其中 B 和 C 在其基础上都有一个 A。但是,如果有人确实从 B 或 C 中继承,那么对象布局就必须不同,因为两者必须共享 A 的副本。

这样做的问题是,当编译器第一次看到 B 和 C 时,它不知道是否有人会继承它们。因此,编译器将不得不依赖于虚拟继承中使用的较慢版本的继承,而不是默认打开的更优化的继承版本。这违反了 C++ 原则“不要为你不使用的东西付费”(零开销原则),您只需为明确使用的语言功能付费。

于 2011-03-02T10:46:49.797 回答
8

为什么虚拟继承需要在 B 和 C 执行,即使歧义在 D?如果它在 D 处会更直观。

在您的示例中, B 和 Cvirtual专门用于要求编译器确保只涉及 A 的一个副本。如果他们不这样做,他们实际上是在说“我需要我自己的 A 基类,我不希望与任何其他派生对象共享它”。这可能是至关重要的。

不想共享虚拟基类的示例

如果 A 是某种容器,则 B 从它派生并存储某种特定类型的对象 - 比如说“Bat”,而 C 存储“Cat”。如果 D 期望 B 和 C 独立提供有关蝙蝠和猫种群的信息,那么如果 C 操作对蝙蝠/与蝙蝠做了某事,或者 B 操作对/与猫做了某事,他们会感到非常惊讶。

想要共享一个虚拟基类的例子

假设 D 需要提供对 A 中的某些函数或数据成员的访问,例如“A::x”...如果 A 由 B 和 C 独立(非虚拟)继承,则编译器无法解析 D ::x 到 B::x 或 C::x 无需程序员明确地消除歧义。这意味着 D 不能用作 A,尽管派生链暗示的不是一个而是两个“is-a”关系(即如果 B“是”A,而 D“是”B,那么用户可以期望/需要使用 D,就好像 D“是”A)。

为什么标准委员会会这样设计这个功能?

virtual继承存在是因为它有时很有用。它由 B 和 C 指定,而不是 D,因为它在 B 和 C 的设计方面是一个侵入性概念,并且对 B 和 C 的封装、内存布局、构造和销毁以及函数调度都有影响。

如果 B 和 C 类来自 3rd 方库,我们该怎么办?

如果 D 需要从两者继承并提供对 A 的访问,但 B 和 C 并非设计为使用虚拟继承且无法更改,则 D 必须负责将任何与 A API 匹配的请求转发给 B 和/ 或 C 和/或可选的另一个 A 它直接继承自(如果它需要可见的“是 A”关系)。如果调用代码知道它正在处理 D(即使通过模板),这可能是实用的,但是通过指向基类的指针对对象的操作将不知道 D 试图执行的管理,整个事情可能是很难做到正确。但这有点像说“如果我需要一个矢量而我只有一个列表怎么办”,“一把锯子而不是螺丝刀”......好吧,充分利用它或得到你真正需要的东西。

编辑:我的回答是指示 B 和 C 类,它们不应在创建派生对象时调用 A 的构造函数,因为它将由 D 调用。

这是其中的一个重要方面,是的。

于 2011-03-02T11:51:36.077 回答
2

除了 templatetypedef 答案之外,可能会指出您也可以将 A 包装成

class AVirt:virtual public A{}; 

并从中继承其他类。在这种情况下,您不需要将其他继承显式标记为虚拟

于 2011-03-02T11:15:15.020 回答
1

问题:为什么虚拟继承需要在 B 和 C 执行,即使歧义在 D?

因为 B 和 C 的方法必须知道它们可能必须处理布局与 B 和 C 自己的布局大不相同的对象。使用单继承这不是问题,因为派生类只是在父类的原始布局之后附加它们的属性。

使用多重继承你不能这样做,因为首先没有单亲的布局。此外(如果你想避免 A 的重复)父母的布局需要在 A 的属性上重叠。C++ 中的多重继承隐藏了相当多的复杂性。

于 2011-03-02T12:19:50.443 回答
0

由于 A 是多重继承的类,因此直接从它派生的类必须这样做是虚拟的。

如果您遇到 B 和 C 都从 A 派生的情况,并且您想要两者都在 D 中并且您不能使用菱形,那么 D 可以仅从 B 和 C 中的一个派生,并具有另一个的实例,通过它它可以转发功能。

解决方法是这样的:

class B : public A; // not your class, cannot change
class C : public A; // not your class, cannot change

class D : public B; // your class, implement the functions of B
class D2 : public C; // your class implement the functions of C

class D
{
   D2 d2;
};
于 2011-03-02T11:46:23.997 回答