【问题标题】:How to properly implement multiple inheritance? [closed]如何正确实现多重继承? [关闭]
【发布时间】:2014-07-13 06:09:27
【问题描述】:

我花了很多时间搜索关于这个主题的信息,但我只能找到碎片,被大量不要使用多重继承的警告所笼罩。

我对多重继承有多糟糕不感兴趣。我对有效的用例也不感兴趣。我收到的信息是,您应该尽可能避免使用它,并且几乎总是有更好的选择。

但我想彻底了解的是,您决定使用多重继承时,如何正确地做到这一点?

我希望看到更彻底解释的子主题是:

  • 多态性的精确机制
    • 混合虚拟和纯虚拟基类
    • 重复函数
  • 内存管理
  • 多层次解决钻石问题
  • 混合公有和私有继承
  • 混合虚拟和非虚拟继承

并且,如果适用的话:

  • C++ 和 C++11 的区别

【问题讨论】:

  • MI 并不邪恶,尤其是在某些习语依赖它的 C++ 上下文中。也就是说,到目前为止你取得了什么成就,即你已经完成了哪些部分的作业?
  • @UlrichEckhardt:这不是家庭作业,我正在自学。我发现虚拟继承在某种程度上结合了虚拟功能,但没有确切的限制。我怀疑内存管理正常进行,但还没有找到任何支持它的东西。我已经尝试过混合公共、私人和虚拟的方式,但它的表现并不完全符合我的预期。我考虑过查找和参考资源,但我认为最好将这个问题的起点保持在初学者的水平,这样,理想情况下,答案可以作为每个人的通用资源。
  • @Aberrant virtual 关键字对于虚函数和虚继承的含义是不同的。
  • 好吧,我想这可能太宽泛了。不过,这似乎确实是许多人想在谷歌上搜索并得到一个一般但彻底的答案的问题类型。我无法确定问题的哪些部分需要缩减或指定,部分原因是我不确定哪些知识真正对于彻底理解多重继承很重要。
  • 考虑到我刚才所说的,最好问“您需要知道什么才能正确使用多重继承?”? (我现在将检查该措辞是否产生任何重复,但到目前为止,我一直在寻找非常具体的问题,例如“我的代码示例有什么问题”)

标签: c++ inheritance c++11 multiple-inheritance


【解决方案1】:

采用以下层次结构:

  • 基类A
  • BCE 继承自 A
  • D 继承自 BC
  • F 继承自 DE

在代码中说:

class A { public: int a; }
class B : public A { }
class C : public A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

或者,图表:

       A     A   A
       |     |   |
       |     |   |
       B     C   E
        \   /   /
         \ /   /
          D   /
           \ /
            F

在这种结构下,每个 B、C 和 E 都拥有自己的 A 副本。接着,D 拥有 B 和 C 的副本,F 拥有 D 和 E 的副本。

这会导致问题:

D d;
d.a = 10; // ERROR! B::a or C::a?

对于这种情况,您可以使用虚拟继承,创建一个“钻石”:

          A      A
         / \     |
        /   \    |
       B     C   E
        \   /   /
         \ /   /
          D   /
           \ /
            F

或者,在代码中:

class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

现在你解决了之前的问题,因为B::aC::a 共享相同的内存,但同样的问题仍然存在,在另一个层面:

F f;
f.a = 10; // ERROR: D::a or E::a ?

这部分我不确定Confirmed:你也可以使用virtual 从 A 继承 E 来解决这里的问题。但我将保持原样,以回答另一点:混合虚拟继承和非虚拟继承。

但考虑到您希望来自FE::a 与相同的F 具有不同的D::a 值。为此,您必须输入您的F

F *f = new F;
(static_cast<D*>(f))->a = 10;
(static_cast<E*>(f))->a = 20;

现在您的F* f 拥有两个不同的A::a 值。

关于内存管理

从上面的例子中学习这些课程:

class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

可以画出如下内存图:

A类:

+---------+
|    A    |
+---------+

对于 B、C 和 E 类:

+---------+
|    B    |
+---------+
     |
     V
+---------+
|    A    |
+---------+

这意味着对于您创建的每个 B、C 和 E 实例,您都会创建另一个 A 实例。

对于 D 类,事情有点复杂:

+---------------------------------------+
|                   D                   |
+---------------------------------------+
       |                         |
       V                         V
+--------------+         +--------------+
|      B       |         |       C      |
+--------------+         +--------------|
       |                         |
       V                         V
+---------------------------------------+
|                   A                   |
+---------------------------------------+

这意味着当您创建一个 D 时,您有一个 B 实例和一个 C 实例。但不是为每个 B 和 C 创建一个新的 A 实例,而是为两者创建一个实例。

对于 F:

+-------------------------------------------------------+
|                           F                           |
+-------------------------------------------------------+
                    |                              |
                    V                              V
+---------------------------------------+     +---------+
|                   D                   |     |    E    |
+---------------------------------------+     +---------+
       |                         |                 |
       V                         V                 |
+--------------+         +--------------+          |   
|      B       |         |       C      |          |
+--------------+         +--------------+          |
       |                         |                 |
       V                         V                 V
+---------------------------------------+     +---------+
|                   A                   |     |    A    |
+---------------------------------------+     +---------+

意思是当你创建一个 F 时,你有:一个 D 的实例和一个 E 的实例。由于 E 没有从 A 虚拟继承,所以在创建 E 时会创建一个新的 A 实例。

关于虚和纯虚方法

参加这些课程:

class A { virtual void f() = 0; }
class B : public A { virtual void f(int value) { std::cout << "bar" << value; } }
class C : public B { virtual void f() { std::cout << "foo"; f(42); } }

A 被称为abstract(有些也称为interface),因为有纯虚函数。

B也是abstract,因为它继承自A并且没有覆盖A::f(void)方法,它是纯虚的,甚至定义了自己的方法(B::f(int)

CBimplementation,因为它确实定义了将B 转换为“完整”类所需的所有函数——它覆盖了A::f(void)

这个答案并不完整,但它给出了一个大致的想法。

【讨论】:

  • 我认为在处理使用虚拟继承的类层次结构时,使用 C 样式转换是一个非常糟糕的主意。
  • C 风格的演员表与static_cast 完全相同。既然您知道fF* 并且F 派生自DE,那么您可以使用C 样式转换(或static_cast)。换句话说,用 C 风格“向下转换”是好的; “向上转型”很糟糕:拥有D* d 并使用F* f = (F*)d - 因为所有F 都有完整的D,但并非所有D 都有完整的F。在这些情况下,您必须使用dynamic_cast
  • @Bruno:不,如果合法,c 样式转换只是静态转换。如果你犯了一个错误,它将变成一个没有错误的 reinterpret-cast,其中一个 static_cast 将无法编译。见stackoverflow.com/questions/332030/…
  • C 风格的演员表与static_cast 完全相同。真的吗?我记得它有much more complex behavior(参见“常规演员表”段落)。
  • 我认为在使用指针、句点时使用 C 样式转换是一个非常糟糕的主意(有一个例外,如前所述:私有基类转换),因为它模棱两可地是 staticreinterpretconst 转换(或多个!)基于变量的类型,如果代码编写者的类型不太正确,则默默地更改:C 风格的转换很脆弱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 2011-11-21
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
相关资源
最近更新 更多