为什么需要在菱形层次结构的中间指定虚拟继承?
我有类的钻石层次结构:
A
/ \
B C
\ /
D
为了避免 D 中存在 A 的两个副本,我们需要在 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 类来自第三方库,我们该怎么办?
编辑:我的答案是指示 B 和 C 类,每当创建其派生对象时,它们不应调用 A 的构造函数,因为它将由 D 调用。
I have diamond hierarchy of classes:
A
/ \
B C
\ /
D
To avoid two copies of A in D, we need to use virtual inheritance at B and C.
class A { };
class B: virtual public A {};
class C: virtual public A { };
class D: public B, public C { };
Question: Why does virtual inheritance needs to be performed at B and C, even though the ambiguity is at D? It would have been more intuitive if it is at D.
Why is this feature designed like this by standards committee?
What can we do if B and C classes are coming from 3rd party library ?
EDIT: My answer was to indicate B and C classes that they should not invoke A's constructor whenever its derived object gets created, as it will be invoked by D.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我不确定他们选择以这种方式设计虚拟继承的确切原因,但我相信原因与对象布局有关。
假设 C++ 的设计方式是为了解决菱形问题,您将在 D 中虚拟继承 B 和 C,而不是在 B 和 C 中虚拟继承 A。现在,B 和 C 的对象布局是什么?好吧,如果没有人尝试从它们那里进行虚拟继承,那么他们每个人都会有自己的 A 副本,并且可以使用标准的优化布局,其中 B 和 C 的基础上都有一个 A。然而,如果有人实际上从 B 或 C 继承,那么对象布局就必须不同,因为两者必须共享 A 的副本。
这样做的问题是,当编译器首先看到 B 和 C,它不知道是否有人要继承它们。因此,编译器必须依靠虚拟继承中使用的较慢的继承版本,而不是默认启用的更优化的继承版本。这违反了 C++ 的“不为不使用的东西付费”的原则(零开销原则),即您只需为明确使用的语言功能付费。
I'm not sure of the exact reason they chose to design virtual inheritance this way, but I believe the reason has to do with object layout.
Suppose that C++ was designed in a way where to resolve the diamond problem, you would virtually inherit B and C in D rather than virtually inheriting A in B and C. Now, what would the object layout for B and C be? Well, if no one ever tries to virtually inherit from them, then they'd each have their own copy of A and could use the standard, optimized layout where B and C each have an A at their base. However, if someone does virtually inherit from either B or C, then the object layout would have to be different because the two would have to share their copy of A.
The problem with this is that when the compiler first sees B and C, it can't know if anyone is going to be inheriting from them. Consequently, the compiler would have to fall back on the slower version of inheritance used in virtual inheritance rather than the more optimized version of inheritance that is turned on by default. This violates the C++ principle of "don't pay what you don't use for," (the zero-overhead principle) where you only pay for language features you explicitly use.
在您的示例中,B 和 C 专门使用
virtual
来要求编译器确保只涉及 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,而无需程序员显式消除歧义。这意味着尽管派生链隐含的不是一个而是两个“is-a”关系,但 D 不能用作 A(即,如果 B“是”A,并且 D“是”B,则用户可以期望/需要使用 D,就好像 D“是”A)。
虚拟继承的存在是因为它有时很有用。它是由 B 和 C 指定的,而不是由 D 指定的,因为它对于 B 和 C 的设计来说是一个侵入性的概念,并且对 B 和 C 的封装、内存布局、构造和销毁以及函数调度也有影响。
如果 D 需要从两者继承并提供对 A 的访问,但 B 和 C 未设计为使用虚拟继承且无法更改,则 D 必须负责将与 A API 匹配的任何请求转发到 B 和/或C和/或可选的另一个它直接继承的A(如果它需要可见的“是A”关系)。如果调用代码知道它正在处理 D (即使通过模板),那么这可能是实用的,但是通过指向基类的指针对对象进行的操作将不知道 D 正在尝试执行的管理,并且整个事情可能是很难做到正确。但这有点像说“如果我需要一个矢量而我只有一个列表怎么办”,“一把锯而不是螺丝刀”......好吧,充分利用它或得到你真正需要的东西。
是的,这是一个重要方面。
In your example, B and C are using
virtual
specifically to ask the compiler to ensure there's only one copy of A involved. If they didn't do this, they're effectively saying "I need my own A base class, I'm not expecting to share it with any other derived object". This could be crucial.Example of not wanting to share a virtual base class
If A was some kind of container, B was derived from it and stored some particular type of object - say "Bat", while C stores "Cat". If D expects to have B and C independently providing information on a population of Bats and Cats they'd be very surprised if a C operation did something to/with the Bats, or a B operation did something to/with the Cats.
Example of wanting to share a virtual base class
Say D needs to provide access to some functions or data members that are in A, say "A::x"... if A is inherited independently (non-virtually) by B and C, then the compiler can't resolve D::x to B::x or C::x without the programmer having to explicitly disambiguate it. This means D can't be used as an A despite having not one but two "is-a" relationships implied by the derivation chain (i.e. if B "is a" A, and D "is a" B, then the user may expect/need to use D as if D "is a" A).
virtual
inheritance exists because it's sometimes useful. It's specified by B and C, rather than D, because it's an intrusive concept in terms of the design of B and C, and also has implications for the encapsulation, memory layout, construction and destruction and function dispatch of B and C.If D needs to inherit from both and provide access to an A, but B and C weren't designed to use virtual inheritance and can't be changed, then D must take responsibility for forwarding any requests matching the A API to either B and/or C and/or optionally another A it directly inherits from (if it needs a visible "is A" relationship). That might be practical if the calling code knows it's dealing with a D (even if via templating), but operations on the object via pointers to the base classes will not know about the management D is attempting to perform, and the whole thing may be very tricky to get right. But it's a bit like saying "what if I need a vector and I've only got a list", "a saw and not a screwdriver"... well, make the most of it or get what you really need.
That's an important aspect of this, yes.
除了 templatetypedef 答案之外,可能会指出您还可以将 A 包装到其中
并从中继承其他类。在这种情况下,您不需要将其他继承显式标记为虚拟继承
In addition to templatetypedef answer, it may be pointed out that you also may wrap A into
and inherit other classes from it. You wil not need to mark explicitly other inheriances as virtual in this case
因为 B 和 C 的方法必须知道它们可能必须处理布局与 B 和 C 自己的布局有很大不同的对象。对于单一继承来说,这不是问题,因为派生类只是将其属性附加在父类的原始布局之后。
对于多重继承,你不能这样做,因为首先没有单一父级的布局。此外(如果你想避免 A 的重复)父级的布局需要在 A 的属性上重叠。 C++ 中的多重继承隐藏了相当多的复杂性。
Because B's and C's methods must know they might have to work on objects whose layout is much different from B's and C's own layouts. With single inheritance it is not a problem, because derived classes just append their attributes after parent's original layout.
With multiple inheritance you cannot to that because there's no single parent's layout in the first place. Moreover (if you want to avoid A's duplication) parents' layouts need to overlap on A's attributes. Multiple inheritance in C++ hides quite a lot of complexity.
由于 A 是多重继承类,因此直接从它派生的类必须如此虚拟。
如果您遇到这样一种情况,B 和 C 都派生自 A,并且您想要两者都在 D 中,并且无法使用菱形,那么 D 可以仅从 B 和 C 之一派生,并拥有另一个的实例,通过该实例它可以转发功能。
解决方法是这样的:
As A is the multiply-inherited class it is those that derive from it directly that have to do so virtual.
If you have a situation where B and C both derive from A and you want both in D and you can't use the diamond, then D can derive from just one of B and C, and have an instance of the other, through which it can forward functions.
workaround something like this: