小编典典

为什么我应该避免 C++ 中的多重继承?

all

使用多重继承是一个好概念还是我可以做其他事情?


阅读 72

收藏
2022-08-08

共1个答案

小编典典

多重继承(缩写为 MI)有 味道 ,这意味着 通常 ,它是出于不好的原因而完成的,并且会在维护者面前反击。

概括

  1. 考虑特征的组合,而不是继承
  2. 警惕恐惧钻石
  3. 考虑继承多个接口而不是对象
  4. 有时,多重继承是正确的。如果是,则使用它。
  5. 准备好在代码审查中捍卫您的多重继承架构

1. 也许作文?

这对于继承来说是正确的,因此对于多重继承来说更是如此。

您的对象真的需要从另一个对象继承吗?ACar不需要从 a 继承Engine来工作,也不需要从
a继承Wheel。ACar有一个Engine和四个Wheel

如果您使用多重继承而不是组合来解决这些问题,那么您做错了。

2. 恐惧钻石

通常,你有一个类A,然后BC两者都继承自A. 并且(不要问我为什么)然后有人决定必须从和D继承。B``C

八年来我遇到过两次这样的问题,这很有趣,因为:

  1. 从一开始就犯了多大的错误(在这两种情况下,D都不应该从两者继承Band C),因为这是糟糕的架构(事实上,C根本不应该存在......)
  2. 维护人员为此付出了多少代价,因为在 C++ 中,父类A在其孙类中出现了两次D,因此,更新一个父字段A::field意味着要么更新它两次(通过B::fieldC::field),要么稍后出现静默错误并崩溃(在 中新建一个指针B::field,然后删除C::field…)

如果这不是你想要的,在 C++ 中使用关键字 virtual 来限定继承可以避免上述双重布局,但无论如何,根据我的经验,你可能做错了什么......

在对象层次结构中,您应该尝试将层次结构保持为树(节点有一个父节点),而不是图。

更多关于钻石的信息(编辑 2017-05-03)

C++ 中的 Diamond of Dread 的真正问题( 假设设计是合理的 - 审查您的代码! ),是 您需要做出选择

  • 该类是否希望A在您的布局中存在两次,这是什么意思?如果是,那么一定要从它继承两次。
  • 如果它应该只存在一次,那么虚拟地继承它。

这种选择是问题所固有的,并且在 C++ 中,与其他语言不同,您实际上可以做到这一点,而无需教条强制您在语言级别进行设计。

但与所有权力一样,这种权力伴随着责任:审查你的设计。

3. 接口

零个或一个具体类的多重继承,以及零个或多个接口通常是可以的,因为你不会遇到上面描述的恐惧钻石。事实上,这就是 Java 中的工作方式。

通常,当 C 继承自A并且B用户可以CA使用B.

在 C++ 中,接口是一个抽象类,它具有:

  1. 它的所有方法都声明为纯虚拟(后缀= 0) (删除2017-05-03)
  2. 没有成员变量

零到一个真实对象以及零个或多个接口的多重继承不被认为是“臭”(至少,不是那么多)。

有关 C++ 抽象接口的更多信息(编辑 2017-05-03)

首先,NVI 模式可用于生成接口,因为 真正的标准是没有状态
(即没有成员变量,除了this)。您的抽象接口的重点是发布合同(“您可以这样称呼我,这样称呼我”),仅此而已。只有抽象虚拟方法的限制应该是一种设计选择,而不是一种义务。

其次,在 C++ 中,从抽象接口虚拟继承是有意义的(即使有额外的成本/间接)。如果你不这样做,并且接口继承在你的层次结构中出现多次,那么你就会有歧义。

第三,面向对象很棒,但它不是C的 The Only Truth Out There TM。使用正确的工具,并永远记住您在 C
中还有其他范式提供不同类型的解决方案。

4. 你真的需要多重继承吗?

有时候是的。

通常,您的C类继承自Aand BAandB是两个不相关的对象(即不在同一层次结构中,没有共同点,不同的概念等)。

例如,您可以拥有一个Nodes具有 X、Y、Z
坐标的系统,能够进行大量几何计算(可能是一个点,几何对象的一部分),并且每个节点都是一个自动代理,能够与其他代理进行通信。

也许您已经可以访问两个库,每个库都有自己的名称空间(使用名称空间的另一个原因......但是您使用名称空间,不是吗?),一个是geo,另一个是ai

所以你有你自己的own::Node派生自ai::Agentgeo::Point

这是您应该问自己是否不应该改用构图的时候。如果own::Node真的真的是 aai::Agent和 a geo::Point,那么组合就不行了。

然后你需要多重继承,让你own::Node根据他们在 3D 空间中的位置与其他代理进行通信。

(您会注意到ai::Agent并且geo::Point完全、完全、完全不相关......这大大降低了多重继承的危险)

其他情况(编辑 2017-05-03)

还有其他情况:

  • 使用(希望是私有的)继承作为实现细节
  • 一些像策略这样的 C++ 习惯用法可以使用多重继承(当每个部分都需要通过 与其他部分进行通信时this
  • 来自 std::exception 的虚拟继承
  • 等等

有时你可以使用组合,有时 MI 更好。关键是:你有选择。负责任地做(并审查您的代码)。

5. 那么,我应该做多重继承吗?

大多数时候,根据我的经验,不会。MI
不是正确的工具,即使它看起来很有效,因为它可以被懒惰的人用来在没有意识到后果的情况下将功能堆在一起(比如Car同时制作 anEngine和 a
Wheel)。

但有时,是的。在那个时候,没有什么比 MI 更有效了。

但是因为 MI 很臭,准备好在代码审查中捍卫你的架构(捍卫它是一件好事,因为如果你不能捍卫它,那么你不应该这样做)。

2022-08-08