【问题标题】:How can I avoid the Diamond of Death when using multiple inheritance?使用多重继承时如何避免死亡钻石?
【发布时间】:2010-09-13 07:55:46
【问题描述】:

http://en.wikipedia.org/wiki/Diamond_problem

我知道这意味着什么,但我可以采取哪些措施来避免它?

【问题讨论】:

  • 我想说“不要使用多重继承”,但这只是一个 cad。我也很想看到一个好的答案。
  • “死亡之钻”有点戏剧化。你到底想知道什么。
  • 它被广泛称为死亡之钻。谷歌一下。
  • Google 告诉我,它通常被称为“钻石问题”,但在 Java 社区中除外,在 Java 社区中,使用更激烈的术语来证明为什么 Java 通过禁止它来“解决”问题是合理的。跨度>
  • 这里没有“死亡”。虚拟继承和“标准”继承都有其用途(但很少使用)。

标签: c++ multiple-inheritance


【解决方案1】:

虚拟继承。这就是它的用途。

【讨论】:

  • 继承层次结构在哪里?
  • 如果 B 和 C 从 A 派生,D 从 B 和 C 派生,则 B 和 C 必须都将 A 声明为虚拟基。具体来说,同一类的每个虚拟继承实例都被折叠成一个类。任何非虚拟的都不会塌陷,导致钻石再次出现。
  • 虽然虚拟继承是解决死亡钻石问题的特性,但我认为有更好的方法来解决这个问题。即从抽象基类(接口类)继承,而不是从多个具体类继承。
【解决方案2】:

我会坚持只使用接口的多重继承。虽然类的多重继承有时很有吸引力,但如果您经常依赖它,它也会让人感到困惑和痛苦。

【讨论】:

  • @Arafangion C++ 确实有接口,尽管它不是 Java 中的语言结构。相反,它们只是纯虚拟基类。
  • @jlh:我承认这一点,尽管我认为虽然 C++ 本身没有接口,但该语言确实允许您实现它们。
  • 我从未见过有人不明白这意味着在这种情况下继承纯抽象类
【解决方案3】:

好吧,Dreaded Diamond 的伟大之处在于它在发生时是一个错误。最好的避免方法是事先弄清楚你的继承结构。例如,我从事的一个项目有查看器和编辑器。 Editor 是 Viewer 的逻辑子类,但由于所有 Viewer 都是子类 - TextViewer、ImageViewer 等,Editor 不是从 Viewer 派生的,因此允许最终的 TextEditor、ImageEditor 类避免菱形。

在无法避免菱形的情况下,使用虚拟继承。然而,对于虚基,最大的警告是虚基的构造函数必须由最派生的类调用,这意味着实际上派生的类无法控制构造函数参数。此外,虚拟基地的存在往往会在通过链施放时导致性能/空间损失,但我不认为除了第一个之外会有太多的损失。

另外,如果您明确说明要使用哪个底座,则始终可以使用菱形。有时这是唯一的方法。

【讨论】:

    【解决方案4】:

    我会建议一个更好的类设计。我确信通过多重继承可以最好地解决一些问题,但请先检查是否有其他方法。

    如果没有,请使用虚函数/接口。

    【讨论】:

    • "先看看有没有别的办法"为什么?
    【解决方案5】:

    一个实际的例子:

    class A {};
    class B : public A {};
    class C : public A {};
    class D : public B, public C {};
    

    注意 D 类如何从 B 和 C 继承。但 B 和 C 都从 A 继承。这将导致 A 类的 2 个副本包含在 vtable 中。

    为了解决这个问题,我们需要虚拟继承。需要虚拟继承的是 A 类。因此,这将解决问题:

    class A {};
    class B : virtual public A {};
    class C : virtual public A {};
    class D : public B, public C {};
    

    【讨论】:

    • 这只是避免A在内存中出现两次,并不能避免Diamond引起的任何问题。见tinyurl.com/abtjcb;你如何实现getDepartment,它总是返回正确的东西?你不能!你的设计有缺陷。见tinyurl.com/ccjnk6
    • 这就是范围的用途。或者,您可以使用 D 类中的“使用”语句。
    • 这个答案不是仅对您可以控制的课程有效吗?如果 B 和 C 在其他人提供的库中,或者它们是代码库的一部分,则您无法更改此“解决方案”根本不起作用。这也违反了 OOP 的整个原则,即基类不应该与派生类有关,但是这里 B 和 C 突然必须更改,因为当天晚些时候添加了一些 D 类。
    • 对我不起作用。得到链接器“vtable”错误。请参阅下面的“答案”帖子。
    【解决方案6】:

    继承是一种强有力的武器。仅在您真正需要时才使用它。过去,钻石继承是我在分类方面走得很远的标志,说用户是“员工”,但他们也是“小部件侦听器”,但也是...

    在这些情况下,很容易遇到多重继承问题。

    我通过使用组合和指向所有者的指针来解决它们:

    之前:

    class Employee : public WidgetListener, public LectureAttendee
    {
    public:
         Employee(int x, int y)
             WidgetListener(x), LectureAttendee(y)
         {}
    };
    

    之后:

    class Employee
    {
    public:
         Employee(int x, int y)
             : listener(this, x), attendee(this, y)
         {}
    
         WidgetListener listener;
         LectureAttendee attendee;
    };
    

    是的,访问权限是不同的,但是如果您可以采用这种方法,而无需复制代码,那就更好了,因为它的功能不那么强大。 (当你别无选择时,你可以节省电力。)

    【讨论】:

    • 而你所做的是你的内存使用量增加了很多。不用了。
    • 组合与继承。战斗!
    【解决方案7】:

    使用委托继承。然后两个类都将指向一个基 A,但必须实现重定向到 A 的方法。它具有将 A 的受保护成员转换为 B、C 和 D 中的“私有”成员的副作用,但现在你不需要需要虚拟,而你没有钻石。

    【讨论】:

      【解决方案8】:
      class A {}; 
      class B : public A {}; 
      class C : public A {}; 
      class D : public B, public C {};
      

      在这种情况下,A 类的属性在 D 类中重复了两次,这会占用更多的内存……所以为了节省内存,我们为 A 类的所有继承属性创建了一个虚拟属性,这些属性存储在 Vtable 中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-13
        • 2019-10-09
        • 2011-02-09
        相关资源
        最近更新 更多