32. 确定你的public继承模式是is-a关系
重要规则:public继承就意味is-a的关系。适用于基类身上的每一件事情一定也适用于继承类身
上,因为每一个继承类对象也都是一个基类对象。
另外两种关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出)。
33. 避免遮盖继承而来的名称
33.1 编译器查找函数顺序:首先查找local作用域,没找到->查找外围作用域(继承类覆盖的),
没找到->查找基类作用域,没找到->查找包含基类的namespace作用域,没找到->最后到
global作用域找。
33.2 继承类内的函数名称会遮盖基类内的函数名称。在public继承下违反了is-a的原则。
33.3 为了让被遮盖的函数可见,可使用using声明式或转交函数(继承类函数里调用基类函数)。
34. 区分接口继承和实现继承
接口继承和实现继承不同。在public继承下,继承类总是继承基类的接口。
接口继承 | 实现继承 | |
---|---|---|
纯虚函数 | 是 | 否 |
非纯(impure)虚函数 | 是 | 缺省实现(允许子类有不同行为) |
非虚函数 | 是 | 强制(不允许子类有不同行为) |
35. 考虑虚函数以外的其它选择
虚函数的替代方案:
方法 | 设计模式 | 优点 | 缺点 | 说明 |
non-virtual interface(NVI) 手法 | Template Method | 非虚函数包装了虚函数,可以在虚函数调用前及调用之后做一些工作 | 对public的虚函数不适合 | 通过public非虚成员函数调用private或protected虚函数 |
函数指针成员变量替换虚函数 | Strategy模式的分解表现 | 1.每个对象可各自拥有自己的计算函数 2.可在运行期改变使用哪个计算函数 | 需要弱化class的封装性,以便非成员函数访问类的非public成员 | 函数指针指向非成员函数,这些函数有相同的参数和返回值。 |
tr1::function成员变量替换虚函数 | Strategy模式的某种形式 | 比函数指针更加灵活 | function可以是函数指针、函数对象、或成员函数指针 | |
将继承体系内的虚函数替换为另一继承体系内的虚函数 | Strategy模式的传统实现 | 增加新的计算策略类很方便 | 策略与对象分离 |
36. 绝不重新定义继承而来的non-virtual函数
任何情况下继承类都不该重新定义一个继承而来的基类non-virtual函数。如果需要重新定义,
那说明这个函数其实应该是virtual函数。
37. 绝不重新定义继承而来的缺省参数值
37.1 可以重定义继承而来的带有缺省参数值的virtual函数实现,但不能重定义它的缺省参数
值。因为virtual函数是动态绑定,而缺省参数值却是静态绑定。
37.2 为什么缺省参数值是静态绑定?
为了运行效率。如果缺省参数值是动态绑定,编译器就必须有某种办法在运行期为虚函
数决定适当的参数缺省值。这比目前实行的"在编译期决定"的机制更慢而且更复杂。为了
程序的执行速度和编译器实现上的简易度,C++做了这样的去舍。
37.3 当你想令virtual函数表现出你所想要的行为但却遇到麻烦时,考虑条款35的替代设计。
38. 通过复合实现has-a或"根据某物实现出"
38.1 复合(composition)的意义和public继承完全不同。
38.2 程序中的对象相当于现实世界的某些事物,这样的对象属于应用域。其它对象如缓冲区、
互斥器、查找树等等,属于实现域。
当复合发生于应用域内的对象之间,表现出has-a的关系;而当它发生于实现域内则是表现
出is-implemented-in-terms-of的关系。
39. 明智而审慎地使用private继承
39.1 private继承的两个规则:
a. 编译器不会自动将一个继承类对象转换为基类对象。
b. 继承来的所有成员,在继承类中都会变成private属性。
39.2 private继承意味is-implemented-in-terms-of,只有实现部分被继承。它通常比复合
(composition)的级别低。尽可能使用复合,必要时使用private继承。private继承主要用
于"想访问基类的protected成分"或为了重新定义继承而来的虚函数。
39.3 EBO(empty base optimization)空白基类最优化
和复合不同,private继承可以EBO(一般只在单一继承)。这对致力于"对象尺寸最小化"的程
序库开发者而言,可能很重要(在意空间的时候)。
39.4 空的类Empty并非不占空间,在大多数编译器中sizeof(Empty)为1。
40. 明智而审慎地使用多重继承
40.1 多重继承比单一继承复杂。它可能导致新的歧义性(多个基类有同名接口时),以及对virtual
继承的需要(否则钻石型多重继承时会重复复制基类成员变量)。
40.2 虚继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果虚基类不带任何数据,将
是最具实用价值的情况。
40.3 两个建议:
a. 非必要不使用virtual bases。尽量使用非虚继承。
b. 如果必须使用虚继承,尽可能避免在其中放置数据。
40.4 多重继承的用途之一:
"public继承某个接口类"和"私有继承某个协助实现的类"的组合(类适配器)。