我花了很多时间搜索关于这个主题的信息,但我只能找到零碎的信息,被大量不要使用多重继承的警告所笼罩。
我对多重继承有多糟糕不感兴趣。我对有效用例也不感兴趣。我得到的信息是,您应该尽可能避免使用它,并且几乎总是有更好的选择。
但我想知道的是,在一个彻底的层面上,您何时决定使用多重继承,您如何正确地使用它?
我希望看到更彻底解释的子主题是:
- 多态性的精确机制
- 内存管理
- 在多个层面解决菱形继承(钻石问题)
- 混合公共(public)和私有(private)继承
- 混合虚拟和非虚拟继承
并且,如果适用的话:
采用以下层次结构:
- 基类
A
B
、C
和 E
继承自 A
D
继承自 B
和 C
F
继承自 D
和 E
用代码来表达:
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::a
和C::a
共享相同的内存,但同样的问题仍然存在,在另一个层面:
F f;
f.a = 10; // ERROR: D::a or E::a ?
这部分我不确定 Confirmed :您也可以使用从 A 继承 E 的 virtual
来解决这里的问题。但我将保留原样,以便回答另一点:混合虚拟和非虚拟继承。
但考虑到您希望 F
中的 E::a
具有来自相同 D::a
的不同值 F
。为此,您必须对 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):
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)
)
C
是 B
的实现
,因为它确实定义了转换 B
所需的所有函数进入一个“完整”的类——它覆盖了 A::f(void)
。
这个答案并不完整,但它给出了一个大概的想法。