条款33:避免遮掩继承而来的名称
看下面的例子:
以作用域为基础的“名称遮掩规则”并没有改变,因此base class内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉了。从名称查找观点来看,Base::mf1和Base::mf3不再被Derived继承!只有在当前域找不到名称的成员才会再到基类去找。。。
如果想继承基类的重载函数,可在Base类的声明里加上:
条款34:区分接口继承和实现继承
有时候,你希望继承类只继承成员函数的接口;有时候你又会希望继承类同时继承函数的接口和实现,但又希望能够覆写它们所继承的实现;有时候你希望继承类同时继承函数的接口和实现,并且不允许覆写任何东西。
Scott提到:
1.成员函数的接口总是会被继承。
2.声明一个pure virtual函数的目的是为了让derived classes只继承函数接口。
3.声明简朴的impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。
4.声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。
条款35:考虑virtual函数以外的其他选择
藉由Function Pointers实现Strategy模式
藉由tr1::function完成Strategy模式
如果我们不再使用函数指针,而是改用一个类型为tr1::function的对象,与函数指针相关的一些约束就没有了。这样的对象可持有(保存)任何可调用物(也就是函数指针、函数对象、或成员函数指针),只要其签名式兼容于需求端。tr1::function的定义与函数指针类似,但是基于tr1的其它辅助功能将使该tr1::function有别于函数指针,下面详细介绍这个东西。。
先来看基本功能代码:
初看起来,和函数指针没有什么区别,但看看下面的代码:
注意上面的ebg2,我们希望用GameLevel类的成员函数health来计算ebg2的健康指数。如果我们使用它,就必须先有一个GameLevel类的对象。因此这里将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为:它指出ebg2的健康计算函数应该总是以currentLevel作为GameLevel对象。bind函数具体细节可以上网搜索。
古典的Strategy模式
将继承体系内的virtual函数替换为另一个体系内的virtual函数。这是Strategy设计模式的传统实现手法。
条款37:绝不重新定义继承而来的缺省参数值
对象的所谓静态类型,就是它在程序中被声明时所采用的类型。考虑一下的class继承体系:
现在考虑这些指针:
本例中ps,pc和pr都被声明为pointer-to-Shape类型,所以它们都以为它为静态类型。注意,不论它们真正指向什么,它们的静态类型都是Shape*。
对象的所谓动态类型则是指“目前所指对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。以上例而言,pc的动态类型是Circle*,pr的动态类型是Rectangle*。ps没有动态类型,因为它尚未指向任何对象。
动态类型一如其名称所示,可在程序执行过程中改变(通常是经由赋值动作):
virtual函数是动态绑定,而缺省参数值确实静态绑定。意思是你可能会在“调用一个定义与derived class内的virtual函数”的同时,却使用base class为它所制定的缺省参数值。
条款39:明智而审慎地使用private继承
如果classes之间的集成关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。这和public继承的情况不同。如果让class D以private形式继承class B,你的用意是为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承意味着只有实现部分被继承,接口部分应略去。如果D以private形式继承B,意思是D对象根据B对象实现而得,再没有其他意涵了。private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。
下面是两种对于Timer类的onTick函数的使用,一种使用private继承,一种采用public继承加复合:
Scott介绍了两个采用第二种方案的理由,第二个是:可以将Widget的编译依存性降至最低。如果Widget继承Timer,当Widget被编译时Timer的定义必须可见,所以定义Widget的那个文件恐怕必须#include Timer.h。但如果WidgetTimer移出Widget之外而Widget内含指针指向一个WidgetTimer,Widget可以只带着一个简单的WidgetTimer声明式,不再需要#include任何与Timer有关的东西。
使用private继承有一种极端情况:当基类为空,而又要考虑节约空间时,以private继承将不会增加继承类的大小,而用复合的方式则不是这样,空的基类也要占用一定空间。
条款40:明智而审慎地使用多重继承
多重继承的意思是继承一个以上的基类,但这些基类可能又有相同的基类,这样就会形成所谓的“砖石型多重继承”。如果这样,C++将会在继承类里复制两次最上层的基类。如果不想出现复制多次的情形,那么就必须采用虚继承。
虚继承在本书中是不被鼓励的行为,因为使用虚继承可能使派生类对象的体积比非虚继承大,而且虚继承的基类的初始化工作是由最下层的派生类负责的,这就是说,虚基类不会自己初始化。
书中还有一个巧妙利用多重继承实现代码重用的例子,感兴趣的可以看看该书195页到198页。这里就不啰嗦了。
条款41:了解隐式接口和编译器多态
面向对象的世界里,显示接口和运行期多态占主导地位,而在模板和泛型编程的世界,隐式接口和编译器多态反而变成更加重要的概念。模板中的对象必须实现的接口是由模板的行为来决定的,模板中的一系列表达式便是模板对象所必需实现的隐式接口。而涉及到对象的任何函数调用,例如operator>和operator!=,有可能造成模板具现化,使这些调用得以成功。这样的具体行为发生在编译期。“以不同的模板参数具现化函数模板”会导致调用不同的函数,这便是所谓的编译期多态。
PS:所谓显示接口和隐式接口的区别,其实就是显示接口是由直接的定义,如成员函数,成员变量等。而隐式接口则有与对象有关的有效表达式推断出对象所支持的接口,比如若对象调用了size函数,那么该对象必然支持size函数的接口。(此书把它说的挺玄乎的。)
加诸于模板参数身上的隐式接口,就像加诸于类对象身上的显示接口一样在编译期完成检查。