Effective C++读书笔记(十)继承与面向对象设计部分(上)
2018-03-14 15:51
267 查看
读书笔记中涉及到的所有代码实例可通过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声明式。
在使用private继承时,派生类想使用基类中的部分方法,可以利用转交函数来实现。
例子见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模式
将函数指针作为参数传入到对象中,令类的某个成员函数来调用传入的函数,则可增加弹性(具体执行哪个函数由参数来决定,函数参数可运行期动态修改)。
籍由tr1::function手法实现Strategy模式
利用tr1::function手法,可以获得更大的灵活性。比如前面使用函数指针方法,如果loseHealthQuickly返回值是short,而不是int,函数指针无法进行转换。而使用tr1::function就可以转换。且tr1::fucntion还可以支持成员函数,函数对象等方式。
补充函数对象,该部分引用自(https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/5813247.html)
既然函数对象与函数指针在使用方式上没什么区别,那为什么要用函数对象呢?很简单,函数对象可以携带附加数据,而指针就不行了。下面例子中,n即为附加数据。
例子见Item35
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 = 4000 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
相关文章推荐
- Effective C++读书笔记(十)继承与面向对象设计部分(上)
- Effective C++读书笔记 第六部分 继承与面向对象设计
- Effective C++读书笔记(十)继承与面向对象设计部分(上)
- Effective C++读书笔记(十)继承与面向对象设计部分(上)
- Effective C++ 精要(第六部分:继承与面向对象设计)
- Effective C++读书笔记(十一)继承与面向对象设计部分(下)
- Effective C++读书笔记(十一)继承与面向对象设计部分(下)
- Effective C++读书笔记(十一)继承与面向对象设计部分(下)
- Effective C++读书笔记(十一)继承与面向对象设计部分(下)
- 除了封装,继承,多态 您还知道那些?-面向对象设计的金字塔
- SWT部分组件的继承问题(引发错误:org.eclipse.swt.SWTException: Subclassing not allowed )
- Effective C++笔记: 继承和面向对象设计(一)
- C++部分——C++继承和多态(2)
- 除了封装,继承,多态 您还知道那些?-面向对象设计的金字塔
- 第六章 继承和面向对象设计(43-44)
- 4.java面向对象语法学习(部分3-继承,组合,代理)
- 泛型的继承和通配符,同时归纳集合部分的面试点
- 《设计模式解析》第二部分 传统面向对象设计的局限性
- Effective C++笔记: 继承和面向对象设计(二)
- C+中继承和部分泛型编程