目录
条款32:确定你的public继承塑模出is-a关系
“public继承”意味is-a(也就是说如果派生类B以public方式继承子基类A那么类B就是一个特殊的类A),如果基类中某个接口不想被派生类实现,比起将该接口定义为virtual,当派生类调用时报错,或许直接不在基类中定义该接口是一个更好的选择(只是一个建议,或许可以考虑复合的模式)。
条款33:避免遮掩继承而来的名称
相对于基类作用域,派生类是一个新的作用域,与其他所有作用域一样,在派生类的作用域内定义的成员会屏蔽其他所有作用域内的同名变量,在派生类的作用域内定义的函数会屏蔽其他所有作用域内的同名函数(无论返回类型和参数列表是否相同)。
在派生类内查找某变量或函数的顺序是先在派生类的定义域内查找,如果在派生类的定义域内没有找到就去基类的作用域内查找,如果还是没找到就去基类的命名空间查找,逐层向上直到全局作用域。
前面提到public继承意味着is-a,如果派生类中遮掩了基类的成员或成员函数,显然就违背了is-a的意义,因此应避免遮掩继承而来的名称
为了让被遮掩的名称再见天日,可以使用using声明式(如果返回类型名称参数列表完全相同可能会引发未定义的错误)或转交函数
private继承时,用转交函数也可以调用继承来的成员函数和成员变量(非private成员)
条款34:区分接口继承和实现继承
在继承时要区分继承的是接口、实现或是两者都继承了
当public继承时,派生类总是会继承基类的接口
如果基类的函数是non-virtual函数,则派生类在继承接口的同时还会继承一份强制的实现
如果基类的函数是简朴的impure virtual函数,则派生类在继承接口的同时还会继承一份缺省的实现(可以重写也可以直接使用)
如果基类的函数是pure virtual函数,则派生类只继承指定接口
我们也可以利用pure virtual函数来提供“除非明确要求否则就不提供的缺省实现”,关键就在于把实现的函数分离为基类的protected成员,在重写的virtual函数中调用实现函数
最常见的两个错误有:
1.将所有函数声明为non-virtual函数
2.将所有成员函数声明为virtual函数(有时是正确的,如之前提到的interface class)
条款35:考虑virtual函数以外的其他选择
在继承中virtual函数的作用主要是提供派生类接口,不提供或只提供缺省的实现(可以说是同样的接口基类和派生类实现的功能不完全相同),要实现这一目标单纯的virtual函数并不是唯一的选择。
1.籍由Non-Virtual Interface手法实现Template Method模式
这个设计的思想是将要调用的接口设计为non-virtual函数,将具体的实现设计为private的virtual函数,由non-virtual的接口调用具体实现,这里需要注意的一个点是派生类是可以重写基类的private虚函数的,如此一来派生类通过non-virtual的接口就可以调用重写的实现,这种写法的优点是通过non-virtual接口可以做一些公共的“事前工作”和“事后工作”(这种做法可以看作是给实现加了个壳)
2.籍由Function Pointers实现Strategy模式
这个设计的的思想是完全不依靠virtual函数来进行不同的实现,具体的方法是将实现封装为类外函数,在类内包含可以调用该类外函数的函数指针,通过向构造函数传入不同的函数来初始化类内的函数指针,最后由接口来调用该函数指针来实现不同的实现,这个方法的优点是不同的对象(不单单是不同派生类)可以有不同的实现,但这一方法也有一个明显的缺点,类外函数无法访问类内的非public成员,要解决这一缺点的方法是弱化封装,友元或更多的public接口
3.籍由tr1::function完成Strategy模式
这一方法与上一个方法的不同是用function来替代函数指针,提供了更加惊人的弹性
4.古典的Strategy模式
将当前基类的接口实现做成一个分离的继承体系中的virtual成员函数,将分离继承体系的类指针作为当前类的private成员,再由接口调用
条款36:绝不重新定义继承而来的non-virtual函数
条款37:绝不重新定义继承而来的缺省参数值(默认参数)
首先明确一个前提,virtual函数属于动态绑定,而virtual函数的缺省参数值属于静态绑定(以基类的为准),因此即使重新定义也不会产生效果,最好的办法就是只声明不定义
条款38:通过复合塑模出has-a或“根据某物实现出”
由之前的条款可以知道,public继承是一个is-a关系,但是对于is-a关系并不适用于所有情况,当我们的类需要具有另一个类的特性但又确定两个类并不能等价时,就需要has-a关系(如人会有一个住址但人并不是一个住址),要实现has-a关系的方法为复合,所谓复合就是将类中需要用到的其他类作为该类的private成员(前面的条款提到,所有的成员都应该为private),并通过接口调用成员来实现相应功能。
条款39:明知而审慎地使用private继承
private继承意味着is-implemented-in-terms of(根据某物实现出)。它通常比复合的级别低。当我们需要is-implemented-in-terms of时尽可能使用复合,必要时才使用private继承。
当派生类需要访问基类中的protected成员或需要重新定义继承而来的virtual函数时可以使用private继承
此外,当类为empty class(不含有任何非static成员变量)时,采用private比采用复合更好,因为empty class实际的大小为0,但是当其作为成员变量时编译器会使其大小为1个字节,再加上内存对齐会浪费更加多的空间
条款40:明智而审慎地使用多重继承
多重继承比单一继承复杂。它可能导致新的歧义,以及对virtual继承的需求(virtual继承用于解决菱形继承问题)
virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有使用价值的情况
多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相组合