EC读书笔记系列之16:条款35、36、37、38、39、40
条款35 考虑virtual函数以外的其他选择
记住:
★virtual函数的替代方案包括NVI手法及Strategy模式的多种形式。NVI手法自身是一个特殊形式的Template Method模式
★将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员
★tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物
------------------------------------------------------------------------------------------
背景:
class GameCharacter {
public:
virtual int healthValue() const; //返回角色的健康指数
...
};
这是常规设计,以下探讨其替代解决方案。
借由non-virtual interface手法实现template method模式:
NVI手法对public virtual函数而言是一个有趣的替代方案。令客户通过public non-virtual成员函数间接调用private virtual函数,称为NVI手法。它是所谓的template method模式的一个独特表现形式(注意此处应将其与看过的设计模式知识联系起来!!!)。此public non-virtual函数称为virtual函数的外覆器(wrapper)。
class GameCharacter {
public:
int healthValue() const { //此即public non-virtual member 函数
... //做一些事前工作
int retVal = doHealthValue(); //做真正的工作
... //做一些事后的工作
return retVal;
}
...
private:
virtual int doHealthValue() const { //派生类可以重新定义它!!!
...
}
};
注意点:
a NVI手法一个优点隐身在上述代码注释“做一些事前、事后工作”之中。事前工作可包括锁定互斥器、制造运转日志记录项、验证class约束条件、验证函数先决条件等;事后工作包括互斥器解锁、验证函数的事后条件、再次验证class约束条件等等。
b 注意一个C++规则:derived classes可重写继承而来的private virtual函数!!!!!!!
----------------
借由函数指针实现策略模式:(注意联系到essential C++笔记中的通过面向过程的方法来实现多态性的数列类例子!!!)
class GameCharacter; //以下函数是计算健康指数的缺省算法
int defaultHealthCalc( const GameCharacter &gc ); class GameCharacter { //类比于strategy模式的context类
public:
typedef int (*HealthCalcFunc)( const GameCharacter & ); explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
: healthFunc( hcf ) { } int healthValue() const {
return healthFunc( *this );
} private:
HealthCalcFunc healthFunc; //类比于context中的策略基类!!!
};
此做法是常见的策略模式的简单应用。拿它和“基于GameCharacter继承体系内之virtual函数”的做法比较,提供了以下弹性:
♢ 同一人物类型之不同实体可以有不同的健康计算函数,如:
class EvilBadGuy : public GameCharacter {
public:
explicit EvilBadGuy( HealthCalcFunc hcf = defaultHealthCalc )
: GameCharacter( hcf ) { }
...
}; int loseHealthQuickly( const GameCharacter & ); //健康指数计算函数1
int loseHealthSlowly( const GameCharacter & ); //健康指数计算函数2 EvilBadGuy ebg1( loseHealthQuickly ); //相同类型的人物搭配不同的健康计算方式
EvilBadGuy ebg2( loseHealthSlowly );
♢ 某已知人物之健康指数计算函数可在运行期变更。如GameCharacter可提供一个成员函数setHealthCalculator用来替换当前的健康指数计算函数。
-------------------------------
借由tr1::function实现策略模式:
即对上面方法的改进:此处不使用function pointer,而是改用一个类型为tr1::function的对象,这样的对象可持有任何可调用物(包括函数指针、仿函数、成员函数指针),只要其签名式兼容于需求端:
class GameCharacter; //如前 //以下函数是计算健康指数的缺省算法
int defaultHealthCalc( const GameCharacter &gc ); //如前 class GameCharacter {
public:
//HealthCalcFunc可以是任何可调用物,可被调用并接收任何兼容于GameCharacter
//之物,返回任何兼容于int的东西
typedef std::tr1::function<int (const GameCharacter &)> HealthCalcFunc; //注意语法形式!!!
explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
: healthFunc( hcf ) { } int healthValue() const {
return healthFunc( *this );
} private:
HealthCalcFunc healthFunc;
};
如今GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针!!!此时使用起来弹性更大:
short calcHealth( const GameCharacter & ); //健康指数计算函数,注意其返回类型为non-int struct HealthCalculator {
int operator()( const GameCharacter & ) const //为健康计算而设计的仿函数
{...}
}; class GameLevel {
public:
float health( const GameCharacter & ) const; //成员函数,也可用来计算健康指数
... //注意其non-int返回类型
}; class EvilBadGuy : public GameCharacter { //同前
...
}; class EyeCandyCharacter : public GameCharacter { //另一人物类型,假设其构造函数与
//EvilBadGuy相同
...
}; EvilBadGuy ebg1( calcHealth ); //人物1,使用其他函数计算健康指数
EyeCandyCharacter ecc1( HealthCalculator() ); //人物2,使用functer计算健康指数 GameLevel currentLevel;
...
EvilBadGuy ebg2( std::tr1::bind( &GameLevel::health, currentLevel, _1 ) );
//人物3,使用某个成员函数计算健康指数,注意此语法形式!!!
------------
总结本条款提出的几个virtual函数的替代方案:
♢ 使用NVI手法,其以public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数(这是模板方法模式的一种特殊形式)
♢ 将virtual函数替换为“函数指针成员变量”(这是策略模式的一种分解表现形式)
♢ 以tr1::function成员变量替换virtual函数 (这也是策略模式的某种形式)
条款36 绝不重写继承而来的non-virtual函数
记住:
★绝不重新定义继承而来的non-virtual函数
条款37 绝不重新定义继承而来的缺省参数值
记住:
★绝不重新定义继承而来的缺省参数值,∵缺省参数值都是静态绑定,而virtual函数---你唯一应该覆写的东西---却是动态绑定
----------------------------------------------------
本条款的讨论主题是“继承一个带有缺省参数值的virtual函数”。
class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
virtual void Draw( ShapeColor color = Red ) const = ;
...
}; class Rectangle : public Shape {
public:
//注意,赋予了不同的缺省参数值,糟糕!!!
virtual void Draw( ShapeColor color = Green ) const;
...
}; class Circle : public Shape {
public:
virtual void Draw( ShapeColor color ) const;
//以上这么写则当客户以对象调用此函数,一定要指定参数值,∵静态绑定
//下这个函数并不从其base继承缺省参数值。但若以指针或引用调用此函数
//可不指定参数值,∵动态绑定下这个函数会从其base继承缺省参数值
...
}; Shape *pc = new Circle;
Shape *pr = new Rectangle; //pr的静态类型是Shape*,动态类型是Rectangle*!!!
pr->Draw(); //此处可不指定参数值,调用Rectangle::Draw( Shape::Red )
此处pr的动态类型是Rectangle*,∴调用的是Rectangle的virtual函数。Rectangle::Draw函数的缺省参数值应该是Green,但由于pr的静态类型是Shape*,∴此一调用的缺省参数值来自Shape class而非Rectangle class!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
--------------
若你试着遵循此条款,且同时提供缺省参数给base和derived class又会怎么样?
class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
virtual void Draw( ShapeColor color = Red ) const = ;
...
}; class Rectangle : public Shape {
public:
virtual void Draw( ShapeColor color = Red ) const;
...
};
这就代码重复了!!!更糟糕的是代码重复又带来了相依性:若Shape内的缺省参数值改变了,所有“重复给定缺省参数值”的那些derived classes也必须改变!!!
--------------------------
当你想令virtual函数表现出你所想要的行为但却遭遇麻烦,聪明的做法是考虑替代设计:
如NVI手法:
class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
void Draw( ShapeColor color = Red ) const { //wrapper!!!
doDraw( color );
}
... private:
virtual void doDraw( ShapeColor color ) const = ;
}; class Rectangle : public Shape {
public:
...
private:
virtual void doDraw( ShapeColor color ) const;
...
};
由于non-virtual函数绝不会被derived classes覆写,这个设计很清楚地使得draw函数的color缺省参数值总是为Red。
条款38 通过复合塑模出has-a或“根据某物实现出”
记住:
★复合的意义与public继承完全不同
★在应用域复合意味has-a;在实现域符合意味is-implemented-in-terms-of(根据某物实现出)
条款39 明智而审慎地使用private继承
记住:
★private继承意味is-implemented-in-terms-of。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这个设计是合理的
★和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要
-----------------------------------------------------------------------------------------------------
private继承的两个典型特点:
1 若class之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象:
class Person {...};
class Student : private Person {...};
void eat( const Person &p );
Person P;
Student s;
eat( p ); //对
eat( s ); //错误!!!
2 基类中的public和protected成员private继承来之后全变private
-------------------------------------
private继承和复合一样意味is-implemented-in-terms-of。但它通常比复合的级别低,即原则上尽量使用复合,必要时才使用private继承,这个必要体现在:空间方面的厉害关系
class Timer {
public:
explicit Timer( int tickFrequency );
virtual void onTick() const;
...
}; class Widget : private Timer {
private:
virtual void onTick() const;
...
};
设计时Timer和Widget并不是将其设计成一个继承体系,即不是is-a关系,我们只想在Widget中用到定时器的onTick而已,所以此处用public继承并不合适,∴选择了private继承。
此设计的一个替代方案如下(使用复合):
class Widget {
private:
class WidgetTimer : public Timer { //类中定义类,可以!!!
public:
virtual void onTick() const;
...
};
WidgetTimer timer; //复合!!!
...
};
下面讲一种必要情况下(即涉及空间利害关系)使用private继承比复合要好:
这个情况非常激进只适用于所处理的class不带任何数据(这样的class无non-static成员变量;无virtual函数,因为virtual函数会给对象带来vptr;也无virtual base classes):
class Empty { };
//此为复合方式
class HoldsAnInt {
private:
int x;
Empty e; //C++规定凡是独立(非附属)对象必须有非0大小
};
这种适用复合设计会有sizeof( HoldsAnInt ) > sizeof( int );
若采用private继承:
class HoldsAnInt : private Empty {
private:
int x;
};
则有sizeof( HoldsAnInt ) == sizeof( int )。这是所谓的EBO(empty base optimization;空白基类最优化)。这样的实践很少增加derived classes的大小,挺好的!!!
注:现实生活中所指的empty class并不真的是空,其往往含有typedefs、enums、static成员变量或non-virtual函数。
条款40 明智而审慎地使用多重继承(未看)
记住:
★多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要
★virtual继承会增加大小、速度、初始化(及赋值)复杂度等成本。若virtual base classes不带任何数据,将是最具实用价值的情况
★多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相组合
EC读书笔记系列之16:条款35、36、37、38、39、40的更多相关文章
- EC读书笔记系列之9:条款16、17
条款16 成对使用new和delete时要采取相同形式 记住: ★若你在new表达式中使用[ ],必须在相应的delete中也使用[ ],反之亦然 -------------------------- ...
- EC读书笔记系列之10:条款16、17
条款18 让接口容易被正确使用,不易被误用 记住: ★“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容 ★“阻止误用”的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的 ...
- EC读书笔记系列之1:条款1、条款2、条款3
条款1:视C++为一个语言联邦 记住: ★C++高效编程守则视状况而变化,这取决于你使用C++的哪一部分 C: Object-oriented c++: Template c++: STL 条款2:尽 ...
- EC读书笔记系列之20:条款53、54、55
条款53 不要轻忽编译器的警告 记住: ★严肃对待编译器发出的警告信息.努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉 ★不要过度依赖编译器的报警能力,∵不同的编译器对待事情的态度 ...
- EC读书笔记系列之19:条款49、50、51、52
条款49 了解new-handler的行为 记住: ★set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用 ★Nothrow new是一个颇为局限的工具,∵其只适用于内存 ...
- EC读书笔记系列之18:条款47、48
条款47 请使用traits classes表现类型信息 记住: ★Traits classes使得“类型相关信息”在编译期可用.它们以templates和“templates特化”完成实现 ★整合重 ...
- EC读书笔记系列之17:条款41、42、43、44、45、46
条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函 ...
- EC读书笔记系列之15:条款32、33、34
条款32 确保你的public继承塑模出is-a关系 记住: ★public继承意味着is-a.适用于base class身上的每一件事情一定也适用于derived class身上,∵每一个deriv ...
- EC读书笔记系列之14:条款26、27、28、29、30、31
条款26 尽可能延后变量定义式的出现时间(Lazy evaluation) 记住: ★尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率 ----------------------- ...
随机推荐
- OpenCV-Python教程(10、直方图均衡化)
相比C++而言,Python适合做原型.本系列的文章介绍如何在Python中用OpenCV图形库,以及与C++调用相应OpenCV函数的不同之处.这篇文章介绍在Python中使用OpenCV和NumP ...
- ASP.NET MVC 3 配置EF自动生成模型
Tools(工具) => 扩展工具 => Nuget Tools(工具) => Nuget=>程序包管理器控制台 Nuget 程序包管理器 => Install-Pac ...
- asp.net 树形控件 $.fn.zTree.init
在网页中通过jquery脚本来构筑树形控件将是一个不错的选择,比如有一个文本框,当鼠标点击的时候,像弹出一个下拉框一样弹出一个树形控件,这似乎是一个不错的控制.下面主要讲讲这种树形控件的实现.为了能使 ...
- java中驼峰与下横线格式字符串互转算法
public static final char UNDERLINE = '_'; /** * 驼峰格式字符串转换为下划线格式字符串 * * @param param * @return */ pub ...
- JQuery获取Checkbox组的值
前台: <div id="addtrtr" style="padding:20px; background-color:#F8F8F8;"> < ...
- jQuery Uploadify上传插件
jQuery Uploadify在ASP.NET MVC3中的使用 1.Uploadify简介 Uploadify是基于jQuery的一种上传插件,支持多文件.带进度条显示上传,在项目开发中常被使用. ...
- NSRangeFromString(<#NSString * _Nonnull aString#>) 和rangeOfString
NSRangeFromString NSString *str1 = @"abcdef"; NSString *str2 = @"1-105"; NSStrin ...
- 互联网大公司的CEO,多是程序员出身
互联网有个现象,大公司的CEO,多是程序员出身.举例如下:------马化腾93年深大计算机系毕业,进入润迅通信从软件工程师做到开发部主管,98年11月与张志东等凑齐50万元注册腾讯公司,99年2月开 ...
- Android_自定义进度条
转载:http://blog.csdn.net/lmj623565791/article/details/43371299 ,本文出自:[张鸿洋的博客] 1.概述 最近需要用进度条,秉着不重复造轮子的 ...
- tempo 2.0 学习记录
最近在做项目时使用了tempo,感觉还不错,但是发现网上对于tempo 2.0 的介绍比较少,我也是在GitHub才找到了比较完整的使用说明,我也简单记录一下自己的使用过程,重新学习一下tempo 2 ...