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

新手上路,请多包涵

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

原文由 Hai 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

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

概括

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

1. 也许作文?

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

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

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

2. 恐惧钻石

通常,您有一个类 A ,然后是 BC 都继承自 A 。然后(不要问我为什么)有人决定 D 必须从 BC 继承。

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

  1. 从一开始就犯了多大的错误(在这两种情况下, D 不应该继承自 BC ) (事实上, 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 中的工作方式。

Usually, what you mean when C inherits from A and B is that users can use C as if it was an A , and /或好像它是 B

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

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

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

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

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

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

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

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

有时候是的。

Usually, your C class is inheriting from A and B , and A and B are two unrelated objects (即不在同一层次结构中,没有共同点,不同的概念等)。

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

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

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

这是您应该问自己是否不应该使用构图的时刻。如果 own::Node 真的既是 ai::Agent 又是 geo::Point ,那么组合就不行了。

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

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

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

还有其他情况:

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

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

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

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

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

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

原文由 paercebal 发布,翻译遵循 CC BY-SA 4.0 许可协议

我们使用埃菲尔。我们有出色的 MI。不用担心。没有问题。轻松管理。有时不使用 MI。然而,它比人们意识到的更有用,因为他们是:A)使用一种不能很好地管理它的危险语言 - 或 - B)对他们多年来围绕 MI 工作的方式感到满意 - 或 - C)其他原因(太多了,无法列出我很确定——请参阅上面的答案)。

对我们来说,使用 Eiffel,MI 和其他任何东西一样自然,是工具箱中的另一个好工具。坦率地说,我们并不担心没有其他人在使用 Eiffel。不用担心。我们对我们拥有的东西很满意,并邀请您来看看。

在您查看时:特别注意 Void 安全性和消除 Null 指针取消引用。当我们都在 MI 周围跳舞时,您的指针正在丢失! :-)

原文由 Liberty Lover 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题