假设类D和E以及F都继承自基类B,那么 C类继承自D和E.
(i)B类出现多少份B类?
(ii)如何使用虚拟继承更改此方案?说明 你的答案。
(iii)Java如何避免许多人需要多重继承 的 在C ++中可能使用多重继承的情况?
以下是我目前的一些想法,但我绝不是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可以使用接口来避免使用多重继承。您可以实现多个接口,但只能扩展一个类。我不知道还有什么可以添加,所以任何输入或相关资源都会有所帮助。
答案 0 :(得分:3)
(i)和(iii)是对的。根据我的经验,大部分时间在C ++中,当我使用多重继承时,这是因为基础是接口(在C ++中没有关键字支持的概念,但它是一个你可以执行的概念)。 / p>
(ii)的第一句是正确的,但是你的第二句话是关于虚函数,它与虚拟继承完全不同。虚拟继承意味着只有B
的一个副本,而D
和E
都具有与其基础相同的副本。函数方面没有区别,但区别在于B
的成员变量(和基类)。
如果有一个函数打印出B
的成员变量foo
;那么在情况(ii)中,这个函数总是打印相同的值,因为只有一个foo
,但是如果(i)从D
基类调用该函数可能会打印一个不同的值来调用它来自E
基类。
“钻石继承”一词用两个词作为一个好的助记符来包装所有这些:)
答案 1 :(得分:1)
尽管推理需要有效,但你似乎已经找到了正确的答案。这里的关键问题是"如果它继承了相同的基类两次,如何布置C实例的内存?"
i)对于类型C的对象,在内存布局中有2个基类B副本。提供的示例是"钻石继承"的情况,因为当你绘制出依赖关系/继承树,你基本上画一颗钻石。 "问题"钻石继承本质上是要求如何将对象放在内存中。 C ++采用了两种方法,一种是快速的方法,一种是复制数据成员,另一种是较慢的方法,即虚拟继承"。采用非虚方法的原因是,如果你继承了一个没有数据成员的类(Java中的接口是什么),那么"复制数据成员"就没有问题,因为它们不存在(见底部的注释)。如果您的计划仅使用单继承,则建议使用非虚拟继承。
ii)如果您有一个virtual class C
,那么这就是C ++语言中您希望编译器执行英雄主义行为以确保只有任何/所有基类的一个副本的方式存在于派生类的内存布局中;我相信这也会带来轻微的性能损失。如果您使用任何' B'来自' C'的成员实例现在,它将始终引用内存中的相同位置。请注意,虚拟继承与函数是否为虚拟无关。
旁白:这也与抽象类的概念完全无关。要在C ++中创建类抽象,请设置任何方法声明= 0,如void foo() = 0;
;对任何方法(包括析构函数)这样做都足以使整个类抽象化。
iii)Java彻底禁止它。在Java中,只有单个继承以及实现任意数量接口的能力。虽然接口确实授予你" is-a"关系和拥有虚函数的能力,他们隐含地避免了C ++与数据布局和钻石继承的问题,因为一个接口不能添加任何数据成员,事实上:没有关于如何解决任何数据成员的困惑'位置。
iii的一个重要扩展是认识到,如果你碰巧两次实现相同的接口,那么虚拟函数调用调度根本不会受到影响。原因是该方法将始终执行相同的操作,即使它在您的虚拟表中有多个副本;它只对你的类的数据起作用,它本身不包含需要消除歧义的数据。