钻石传承

Diamond inheritance

本文关键字:传承 钻石      更新时间:2023-10-16

假设类 D 和 E 和 F 都继承自基类 B,并且 类 C 继承自 D 和 E。

(i( C类中出现了多少份B类?

(二( 使用虚拟继承将如何改变这种情况?解释 你的答案。

(iii(Java如何避免对许多人的多重继承的需求 的 在C++中可能使用多重继承的情况?

以下是我目前的一些想法,但我绝不是C++方面的专家!

(i) 如果 C 继承自 D 和 E 这两个 B 的子类,那么 D 和 E 在技术上是它们的超类的副本吗?那么如果 C 继承自 D 和 E,则意味着 C 中有 2 个 B 副本。

(ii) 使用虚拟有点类似于在Java中使用抽象(我认为(。现在考虑到这一点,这意味着 C 中不会有多个 B 副本,因为实例化将被级联到所需的级别。我不知道如何措辞我的解释,但说 B 有一个名为 print(( 的函数,它打印"我是 B",C 覆盖这个函数打印"我是 C"。如果你在没有 virtual 的情况下在 C 上调用 print((,你最终会打印 "i am B",使用 virtual 意味着它会打印 "i am C"。

(iii) 我的想法是Java可以使用接口来避免使用多重继承。您可以实现多个接口,但只能扩展一个类。我不确定这里还有什么要添加的,所以任何输入或相关资源都会有所帮助。

(

i(和(iii(是正确的。无论如何,根据我的经验,大多数时候C++当我使用多重继承时,这是因为基础是接口(这个概念在C++中没有关键字支持,但无论如何它都是你可以执行的概念(。

(ii(的第一句话是对的,但是你的第二句话说的是虚函数,这与虚拟继承完全不同。 虚拟继承意味着B只有一个副本,并且DE都有相同的副本作为它们的基础。在函数方面没有区别,但区别在于B的成员变量(和基类(。

如果有一个函数打印出B的成员变量foo;那么在(ii(的情况下,这个函数总是打印相同的值,因为只有一个foo,但是在(i(的情况下,从D基类调用该函数可能会打印一个不同的值,从E基类调用它。

"钻石继承"一词用两个词来概括这一切,作为一个很好的助记符:)

你似乎基本上已经得出了正确的答案,尽管推理需要努力。这里的关键问题是"如果 C 实例继承同一个基类两次,如何布局它的内存?

i( 对于 C 类型的对象,在内存布局中有 2 个基类 B 的副本。提供的示例是"钻石继承"的情况,因为当您绘制依赖/继承树时,您实际上是绘制钻石。钻石继承的"问题"本质上是询问如何在内存中布置对象。C++采用了两种方法,一种是快速的,复制数据成员,另一种是较慢的方法,即"虚拟继承"。采用非虚拟方法的原因是,如果你继承了一个没有数据成员的类(Java中的接口(,那么"复制数据成员"就没有问题,因为它们不存在(见我在底部的注释(。如果您的计划仅使用单个继承,则还建议使用非虚拟继承。

ii( 如果你有一个virtual class C,那么这就是用C++语言表达的方式,你希望编译器执行英雄主义行为,以确保派生类的内存布局中只存在任何/所有基类的一个副本;我相信这也会导致轻微的性能下降。如果您现在使用"C"实例中的任何"B"成员,它将始终引用内存中的同一位置。请注意,虚拟继承与函数是否为虚拟函数无关。

旁白:这也与抽象类的概念完全无关。要在C++中使类抽象,请设置任意方法声明 = 0,如 void foo() = 0; ;对任何方法(包括析构函数(这样做就足以使整个类抽象。

iii(Java完全禁止它。在Java中,只有单个继承以及实现任意数量接口的能力。虽然接口确实授予您"is-a"关系和具有虚函数的能力,但它们隐含地避免了C++在数据布局和菱形继承方面遇到的问题,因为接口不能添加任何数据成员,事实上:对于如何解析任何数据成员的位置没有混淆。

iii的一个重要扩展是意识到,如果你碰巧"实现同一个接口两次",虚拟函数调用调度根本不会受到影响。原因是该方法将始终执行相同的操作,即使虚拟表中有多个副本也是如此;它只作用于类的数据,它本身不包含需要消除歧义的数据。