常情况下, 组合 (composition) 比继承 (inheritance) 更合适 。如果使用继承,只用public继承。 允许接口继承,尽量不使用实现继承。
定义
当子类继承基类时, 子类会包含基类定义的所有数据及操作. 接口继承 (interface inheritance) 指从纯抽象基类 (pure abstract base class, 不包含状态或方法定义) 继承; 所有其他继承都是 实现继承 (implementation inheritance).
优点
实现继承复用了基类代码, 因此可以减少代码量. 继承是在编译时声明的, 因此您和编译器都可以理解并检查错误. 接口继承可以强制一个类公开特定 API. 当类没有定义 API 中的方法时, 编译器可以检测到错误.
缺点
对于实现继承, 子类的实现代码散布在父类和子类之间, 因此更难理解. 子类不能重写 (override) 父类的非虚函数, 因此无法修改其实现.
多重继承的问题更严重, 因为这样通常会产生更大的性能开销 (事实上, 多重继承相比单继承的性能损失大于虚方法相比普通方法的性能损失). 此外, 这容易产生 菱形继承 (diamond inheritance) 的模式, 造成歧义、混乱和严重错误.
结论
所有继承都应该使用 public
的访问权限. 如果要实现私有继承, 可以将基类对象作为成员变量保存. 当您不希望您的类被继承时, 可以使用 final
关键字.
不要过度使用实现继承. 组合 (composition) 通常更合适. 尽量只在 “什么是什么” (“is-a”, YuleFox 注: 其他 “has-a” 情况下请使用组合) 关系的情况下使用继承: 如果 Bar
是一种 Foo
, 那么 Bar
才能继承 Foo
.
只将子类可能需要访问的成员函数设为 protected
. 注意, 数据成员应该是私有的.
明确使用 override
或 final
(较少使用) 关键字限定重写的虚函数或者虚析构函数. 重写时不要使用 virtual
关键字. 原因: 如果函数带有 override
或 final
关键字, 却没有正确重写基类的虚函数, 会导致编译错误, 有助于发现常见笔误. 这些限定符相当于文档. 如果不用限定符, 读者必须检查所有祖先类才能确定函数是否是虚函数.
允许多重继承, 但强烈建议避免多重实现继承.