读书笔记中涉及到的所有代码实例可通过https://github.com/LuanZheng/EffectiveCPlusPlus.git进行下载获得。
Item32 确定你的public继承塑模出is-a关系
以C++进行面向对象编程,最重要的一个原则是:public inheritance意味“is-a”的关系。适用于base class身上的任何一件事情也适用于derived class.
Item33 避免遮盖继承而来的名称
C++的名称遮盖规则所做的唯一事情就是:遮盖名称。至于名称是否应和相同或不同的类型,并不重要。
派生类作用域被嵌套在基类作用域内。
local作用域>派生类作用域>基类作用域>namespace作用域>global作用域
如果继承的派生类的方法遮盖了基类的方法,而在派生类中又期望调用基类的被遮盖的方法,那么必须为那些原本会被遮盖的方法在派生类中添加using声明式。
class Derived : public Base
{
public:
using Base::mf1; //使用using之后,可以使得基类中被遮盖的函数在派生类中重见光明
using Base::mf3;
void mf1(); //纯虚函数在派生类中需要有具体实现
void mf1(int i, int j); //派生类中的mf1遮盖了基类的所有mf1,不论参数是否匹配
void mf3(); //派生类中的mf3遮盖了基类的所有mf3,不论参数是否匹配
void mf5();
};
在使用private继承时,派生类想使用基类中的部分方法,可以利用转交函数来实现。
#ifndef _PRIVATE_DERIVED_H_
#define _PRIVATE_DERIVED_H_
#include "Base.h"
class PrivateDerived : private Base
{
public:
//inline转交函数,在此处不用using,因为private继承,目的本在于不要暴露base class全部接口
void mf1() { Base::mf1(); }
};
#endif // !_PRIVATE_DERIVED_H_
例子见Item33
Item34 区分接口继承和实现继承
pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。
声明一个pure virtual函数的目的是为了让派生类只继承函数接口。
声明简朴的(非纯)虚函数的目的,是让派生类继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了让派生类继承该函数的接口及一份强制性实现。
当派生类增加种类时,可能导致原来的行为发生变化。比如,原来AirplaneModelA和AirplaneModelB均继承自Airplane。而Airplane中的方法fly()适合ModelA&ModelB。但若引进一种新的AirplaneModelC,且AirplaneModelC的飞行方式与A,B不同,但使用者可能会犯错,仍旧使用基类的fly方法作用在ModelC上。在这种情况下,将基类的fly方法改为纯虚函数,就保证了每个派生类都需要重新定义和实现他,因此,也就强制避免前面错误发生的可能性。若ModelA&B仍想共用原来的方法,避免代码的重复,可以在基类中重新定义一个新的fly方法,defaultfly,而另ModelA&B的fly方法简单调用基类的defaultfly方法即可。
另一种方法是,借用“pure virtual函数必须在派生类中重新声明,但它们也可以拥有自己的实现”这一事实,让派生类的fly方法调用基类的同名fly方法(虽然是pure virtual)。
例子见Item34
Item35 考虑virtual函数以外的其他选择
籍由Non-Virtual Interface手法实现Template Method模式
令“客户通过public non-virtual成员函数间接调用private virtual函数”,称为NVI手法。
NVI手法涉及在派生类内重新定义private virtual函数。但这里并不存在矛盾。“重新定义virtual函数”表示某些事“如何”被完成,“调用virtual函数”则表示它“何时”被完成。NVI手法允许派生类重新定义虚函数,从而赋予它们“如何实现机能”的控制能力,但基类保留诉说“函数何时被调用”的权利。
籍由Function Pointers手法实现Strategy模式
将函数指针作为参数传入到对象中,令类的某个成员函数来调用传入的函数,则可增加弹性(具体执行哪个函数由参数来决定,函数参数可运行期动态修改)。
typedef int(*HealthCalcFunc)(const GameCharacterFP&); //Function points
//弹性:同一类型可以有不用的健康计算函数
//弹性:健康计算函数可在运行期动态变更
GameCharacterFP* gcFPQ = new GameCharacterFPA(loseHealthQuickly);
GameCharacterFP* gcFPS = new GameCharacterFPA(loseHealthSlowly);
籍由tr1::function手法实现Strategy模式
利用tr1::function手法,可以获得更大的灵活性。比如前面使用函数指针方法,如果loseHealthQuickly返回值是short,而不是int,函数指针无法进行转换。而使用tr1::function就可以转换。且tr1::fucntion还可以支持成员函数,函数对象等方式。
1>e:\gitrepository\effectivecplusplus\effectivecplusplus\item35\main.cpp(24): error C2664: “GameCharacterFPA::GameCharacterFPA(GameCharacterFPA &&)”: 无法将参数 1 从“short (__cdecl *)(const GameCharacterFP &)”转换为“GameCharacterFP::HealthCalcFunc”
//TR1::function
GameCharacterATR1Func* gcATR1F = new GameCharacterATR1Func(loseHealthQuicklyTR1); //与FP方法使用起来相同,short会自动转int
gcATR1F->healthValue();
//利用成员函数来计算的方法
GameLevel currentLevel;
GameCharacterATR1Func* gcATR1FMemberCalc =
new GameCharacterATR1Func(std::tr1::bind(&GameLevel::health, currentLevel, std::tr1::placeholders::_1));
gcATR1FMemberCalc->healthValue();
//利用函数对象来实现
std::cout << std::endl;
GameCharacterATR1Func* gcATR1FuncObj =
new GameCharacterATR1Func((*gcATR1FMemberCalc)());
(*gcATR1FuncObj)();
补充函数对象,该部分引用自(https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/5813247.html)
既然函数对象与函数指针在使用方式上没什么区别,那为什么要用函数对象呢?很简单,函数对象可以携带附加数据,而指针就不行了。下面例子中,n即为附加数据。
class Less
{
public:
Less(int num) :n(num) {}
bool operator()(int value)
{
return value < n; //可以使用n,n为附加数据。
}
private:
int n; //这里的n即为前面提到的附加数据
};
int main()
{
Less isLess(10);
cout << isLess(9) << " " << isLess(12); // 输出 true false
system("pause");
return 0;
}
例子见Item35