侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

条款40 明智而审慎地使用多重继承

如此肤浅
2022-06-09 / 0 评论 / 0 点赞 / 23 阅读 / 1,941 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-09,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 多重继承比单一继承复杂。它可能导致新的歧义性,以及对 virtual 继承的需要。
2. virtual 继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果 virtual base classes 不带任何数据,将是最具实用价值的情况。
3. 多重继承的确有正当用途。其中一个情节涉及“public 继承某个 Interface class”和“private 继承某个协助实现的 class”的两相组合。

继承分为多重继承(multiple inheritance; MI)和单一继承(single inheritance; SI)。

多重继承引发的歧义

当使用 MI 时,程序有可能从一个以上的 base classes 继承相同名称,这会导致歧义,例如:

class BorrowableItem { // 图书馆允许你借某些东西
public:
	void checkOut(); // 离开图书馆时进行检查
    ...
};

class ElectronicGadget {
private:
	bool checkOut() const; // 执行自我检查,返回是否执行成功
    ...
};

// 多重继承
class MP3Player: public BorrowableItem, public ElectronicGadget
{ ... }; // 这里class的定义不是我们关心的重点

MP3Player mp;
mp.checkOut(); // 发生歧义!调用的是哪个checkOut呢?

注意此例之中对 checkout 的调用是歧义的,即使两个函数之中只有一个可取用(BorrowableItem 内的 checkOut 是 public,ElectronicGadget 内的却是 private)。这与 C++ 用来解析重载函数调用的规则相符:在看到是否有个函数可取用之前,C++ 首先确认这个函数对此调用之言是最佳匹配。找出最佳匹配函数后才检验其可取用性。本例的两个 checkOuts 有相同的匹配程度(因此才造成歧义),没有所谓最佳匹配。因此 ElectronicGadget::checkout 的可取用性也就从未被编译器审查。

为解决这个歧义,你必须指明要调用哪一个 base class 内的函数:

mp.BorrowableItem::checkOut();

// 注意:下面的调用会出错,
// 因为ElectronicGadget中的checkOut是private
mp.ElectronicGadget::checkOut();

菱形继承

class File { ... };
class InputFile: public File { ... };
class OutputFile: public File { ... };
class IOFile: public InputFile, public OutputFile { ... };

image-1654742100898
上述代码产生一个问题:是否允许让 base class 内的成员变量经过每一条路径被复制?假设 File class 有个成员变量 fileName,那么 IOFile 内该有多少笔这个名称的数据呢?
角度一: IOFile 从其每一个 base class 继承一份,所以其对象内应该有两份 fileName 成员变量。
角度二: 简单的逻辑告诉我们,IOFile 对象只该有一个文件名称,所以它继承自两个 base classes 而来的 fileName 不该重复。

实际情况是两个角度 C++ 都支持——缺省的做法是执行复制(角度一)。如果你想要角度二的效果,你必须令那个带有此数据的 class(也就是 File)成为一个 virtual base class。为了这样做,你必须令所有直接继承它的 classes 采用“virtual”继承:

class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile, public OutputFile { ... };

image-1654742632812

但使用 virtual 后会带来一定的内存占用、访问速度变慢以及不直观等缺点。因此,①非必要不使用 virtual bases,平常请使用 non-virtual 继承;②如果必须使用 vitrual base classes,尽可能避免在其中放置数据。

总结

多重继承只是面向对象工具箱里的一个工具而已。和单一继承比较,它通常比较复杂,使用上也比较难以理解,所以如果你有个单一继承的设计方案,而它大约等价于一个多重继承设计方案,那么单一继承设计方案几乎一定比较受欢迎。如果你唯一能够提出的设计方案涉及多重继承,你应该更努力想一想——几乎可以说一定会有某些方案让单一继承行得通。然而多重继承有时候的确是完成任务之最简洁、最易维护、最合理的做法,果真如此就别害怕使用它。

0

评论区