[C++ 学习笔记 13] Effective C++ 条款 32-40
第六章 继承与面向对象设计
32、确定你的 public 继承塑模出 is-a 关系
“public 继承”意味 is-a。适用于 base classes 身上的每一件事一定也适用于 derived classes 身上,因为每一个 derived classes 对象也都是一个 base class 对象。
classes 之间的关系除了 is-a 之外,还有 has-a(有一个)和 is-implemented-in-terms-of(根据某物实现出)两种常见的关系。
33、避免遮掩继承而来的名称
derived classes 内的名称会掩盖 base classes 内的名称。在 public 继承下从来没有人希望如此。
为了让被掩盖的名称再见天日,可使用 using 声明式或转交函数(forwarding function)。
34、区分接口继承和实现继承
接口继承和实现继承不同。在 public 继承之下,derived classes 总是继承 base class 的接口。
成员函数的接口总是会被继承。
pure virtual 函数有两个最突出的特征:他们必须被任何“继承了它们”的具象 class 重新声明,而且它们在抽象 class 中通常没有定义。
声明一个 pure virtual 函数的目的是为了让 derived classes 只继承函数接口。
声明简朴的(非纯)impure virtual 函数的目的,是让 derived classes 继承该函数的接口和缺省实现。
声明 non-virtual 函数的目的是为了令 derived classes 继承函数的接口及一份强制性实现。
35、考虑 virtual 函数以外的其它选择
藉由 Non-Virtual 手法实现 Template Method 模式
class GameCharacter {
public:
int HealthValue() const // derived classes 不重新定义它
{
...
int value = DoHealthValue();
...
return value;
}
...
private: // 不是必须 private
virtual int DoHealthValue() const // derived classes 可重新定义它
{
... // 缺省算法
}
};
令客户通过 public non-virtual 成员函数间接调用 private virtual 函数,称之为 non-virtual interface(NVI)手法。它是 Template Method 设计模式的一个独特表现形式。non-virtual 函数称为 virtual 函数的外覆器(wrapper)。
藉由 Function Pointers 手法实现 Strategy 模式
缺点:将机能从成员函数移到 class 外部函数,导致非成员函数无法访问 class 的 non-public 成员。
藉由 std::function 手法实现 Strategy 模式
古典的 Strategy 模式
古典的 Strategy 模式会将健康函数做成一个分离的继承体系中的 virtual 成员函数。
36、绝不重新定义继承而来的 non-virtual 函数
class 内声明一个 non-virtual 函数会为该 class 建立起一份不变性(invariant),凌驾其特异性(specialization)。
37、绝不重新定义继承而来的缺省参数值
本条款的讨论局限于“继承一个带有缺省参数值的 virtual 函数”,绝对不要新定义继承而来的缺省参数值,因为缺省参数都是静态绑定,而 virtual 函数——你唯一应该复写的东西——却是动态绑定。
38、通过复合塑模出 has-a 或“根据某物实现出”
复合(composition)的意义和 public 继承完全不同。
在应用域(application domain),复合意味 has-a(有一个)。在实现域(implementation domain),复合意味 is-implemented-in-terms-of(根据某物实现出)。
39、明智而审慎地使用 private 继承
Private 继承意味 is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当 derived class 需要访问 protected base class 的成员,或需要重新定义继承而来的 virtual 函数时,这么设计是合理的。
和复合(composition)不同,private 继承可以造成 empty base 最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
40、明智而审慎地使用多重继承
多重继承比单一继承复杂。它可能导致新的歧义性,以及对 virtual 继承的需要。
virtual 继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果 virtual base classes 不带任何数据,将是最具实用价值的情况。
多重继承的确有正当用途。其中一个情节涉及“public 继承某个 Interface class”和“private 继承某个协助实现的 class”的两相组合。