我的问题与旧问题相同,但我还不明白给出的答案: Diamond Problem
在钻石问题中,D继承自B和C,它们都继承自A,而B和C都覆盖A中的方法foo。
假设改为三角形:没有A,D继承自B和C,它们都实现了方法foo。
据我了解,如果没有特殊处理,钻石或三角形中的以下内容将不明确,因为不清楚要调用哪个foo:
D d = new D;
d.foo();
所以我仍然不确定是什么原因造成钻石问题而不是更普遍的多重继承问题。看起来你需要提供一些方法来消除歧义,甚至在“三角”问题中。
答案 0 :(得分:1)
正如我在评论中提到的,一些问题与通常如何实施动态调度有关。
假设采用vtable方法,任何特定类型必须能够生成一个vtable,允许将其视为自身或其任何超类型。在单继承下,这可以非常容易地实现,因为每个类型的vtable都可以以与其直接超类型相同的vtable布局开始,然后是它引入的任何新成员。
E.g。如果B
有两种方法
vtable_B
Slot # Method
1 B.foo
2 B.bar
D
继承自B
,覆盖bar
并介绍baz
:
vtable_SI_D
Slot # Method
1 B.foo
2 D.bar
3 D.baz
由于D
没有覆盖foo
,因此它只会复制它在{1}的vtable中为插槽#1找到的任何条目。
然后,使用B
到D
变量的任何代码都只会使用#1和#2插槽,一切正常。
然而,引入多重继承,您可能无法使用单个vtable。假设我们现在介绍B
,它也有C
和foo
方法。现在,当bar
投放到D
时,我们需要使用不同的vtable:
B
或vtable_MI_D_as_B
Slot # Method
1 B.foo
2 D.bar
:
C
这些是明确的 1 。问题是试图填写vtable_MI_D_as_C
Slot # Method
1 C.foo
2 D.bar
的vtable,当它没有投射到任何东西时:
D
因此,您要纠正三角形继承确实会引发一些问题。但是,因为我们将Slot # Method
1 <what goes here>
2 D.bar
3 D.baz
的不同 vtable用作D
(而不是D
作为D
或B
})我们可以简单地省略 Slot#1的条目,并且调用C
是非法的(在简单的情况下,D.foo
的定义中没有进一步陈述,例如使用D
s B
或覆盖foo
):
foo
现在让我们介绍vtable_MI_D
Slot # Method
2 D.bar
3 D.baz
并让它定义A
,回到经典的钻石图案。所以foo
vtable是:
A
vtable_A
Slot # Method
1 A.foo
和B
如上所述。我们可以按照上面C
完全相同的方法,除了另外一个问题。我们必须提供D
施放的vtable作为D
。我们无法只省略第1个插槽 - 处理A
的代码希望能够调用A
。而且,我们无法复制foo
或B
vtable中的条目,因为它们具有不同的值,而且它们都是直接的超类型。
我相信,这是为什么通常使用钻石图案的要点 - 因为我们无法实现&#34;您无法调用{{ 1}} C
&#34;统治并完成它。
1 此处值得观察的是foo
和D
vtables中的#1和#2插槽完全无关。 vtable_MI_D_as_B
方法的vtable_MI_D_as_C
方法和插槽#6的C
方法可能有{2}位。具有相同名称的方法不一定会共享&#34;相同的&#34;插槽。
这与后来对钻石继承模式的讨论形成鲜明对比,其中插槽#1确实是所有类型的相同插槽。
答案 1 :(得分:0)
D d = new D();
d.foo();
调用将是不明确的,并且会出现编译器错误。您将被迫指定您想要的foo版本
(B)d.foo();
(C)d.foo();
没问题。无论A和B是否都继承A,这都适用。问题是你有:
A a = new D();
a.foo();
这也是模棱两可的,尽管A显然只有一个版本的foo。如果您获得A并且它恰好是D,即使您在对象上调用方法,也会出现错误。如果你必须测试它是否是D,那么在调用foo之前决定是否转换为B或C,这会破坏多态性
编辑以回复评论:
我有一个方法
void DoStuff(A a)
{
a.foo();
}
我传入D.应该调用哪个版本的foo()?如果DoStuff在由已定义B,C和D的其他代码引用的库中,DoStuff()将不知道如何处理它,因为您不能期望库维护者为每个可能的对象实现覆盖继承自A