繁体   English   中英

钻石继承

[英]Diamond inheritance

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

(i)B类有多少副本出现在C类?

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

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

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

(i)如果C继承自作为B的子类的D和E,那么D和E在技术上是否是他们的超类的副本? 然后,如果C继承自D和E,那意味着C中有2个B副本。

(ii)使用虚拟有点类似于在Java中使用Abstract(我认为)。 现在给出这个,这意味着在C中不会有多个B副本,因为实例化将被级联到所需的级别。 我不确定如何说出我的解释但是说B有一个叫做print()的函数打印“我是B”而C覆盖了这个函数put print“我是C”。 如果您在没有虚拟的情况下在C上调用print(),则最终打印“我是B”,使用虚拟将意味着它将打印“我是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的一个重要扩展是认识到,如果你碰巧“两次实现相同的接口”,虚拟函数调用不会受到影响。 原因是该方法将始终执行相同的操作,即使它在您的虚拟表中有多个副本; 它只对您的类的数据起作用,它本身不包含需要消除歧义的数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM