effective c++:virtual函数的替代方案
绝不重新定义继承来的缺省值
首先明确下,虚函数是动态绑定,缺省参数值是静态绑定
// a class for geometric shapes
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
// all shapes must offer a function to draw themselves
virtual void draw(ShapeColor color = Red) const = ;
...
};
class Rectangle: public Shape {
public:
// notice the different default parameter value — bad!
virtual void draw(ShapeColor color = Green) const;
...
};
class Circle: public Shape {
public:
virtual void draw(ShapeColor color) const;
...
};
当我们这样指定这些指针时。
Shape *ps; // static type = Shape*
Shape *pc = new Circle; // static type = Shape*
Shape *pr = new Rectangle; // static type = Shape*
三个对象的静态类型均为Shape*,这时调用它们的虚函数:
pc->draw(Shape::Red); // calls Circle::draw(Shape::Red)
pr->draw(Shape::Red); // calls Rectangle::draw(Shape::Red)
由于虚函数是动态绑定的,所以pc,pr调用各自的draw没错,但是却是用的时shape中draw指定的Red参数值,当运行这段代码时,画出来的图像可能就是个四不像。
所以如果基类成员虚函数中定义了缺省值,就不能用常规的方法来使用它,我们需要考虑它的替代设计:
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const // now non-virtual
{
doDraw(color); // calls a virtual
}
...
private:
virtual void doDraw(ShapeColor color) const = ; // the actual work is
}; // done in this func class Rectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const; // note lack of a
... // default param val.
};
派生类可以重新定义虚函数,而且可以指定自己独有的color,而non-virtual函数draw按照c++设计原则是不应该被重写的。这种方法称为non-virtual interface,但是有一些函数根据实际需求必须是public,比方多态性质的基类析构函数,这样一来就不能使用NVI了。
tr1::function实现Strategy设计模式
class GameCharacter; // as before
int defaultHealthCalc(const GameCharacter& gc); // as before
class GameCharacter {
public:
// HealthCalcFunc is any callable entity that can be called with
// anything compatible with a GameCharacter and that returns anything
// compatible with an int; see below for details
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
: healthFunc(hcf )
{}
int healthValue() const
{ return healthFunc(*this); }
...
private:
HealthCalcFunc healthFunc;
};
上例中我们把HealthCalcFunc声明为一个typedef,他接受一个GameCharacter引用类型的参数,返回int.tr1::function的功能是使可调物的参数隐式的转换为const GameCharacter&,同理返回类型隐式的转换为int。
我们可以调用改函数进行计算,使用这种方式来替代虚函数的设计避免了NVI的问题,又展现了出色的兼容性。
short calcHealth(const GameCharacter&); // health calculation
// function; note
// non-int return type
struct HealthCalculator { // class for health
int operator()(const GameCharacter&) const // calculation function
{ ... } // objects
};
class GameLevel {
public:
float health(const GameCharacter&) const; // health calculation
... // mem function; note
}; // non-int return type
class EvilBadGuy: public GameCharacter { // as before
...
};
class EyeCandyCharacter: public GameCharacter { // another character
... // type; assume same
}; // constructor as
// EvilBadGuy
EvilBadGuy ebg1(calcHealth); // character using a
// health calculation
// function
EyeCandyCharacter ecc1(HealthCalculator()); // character using a
// health calculation
// function object
GameLevel currentLevel;
...
EvilBadGuy ebg2( // character using a
std::tr1::bind(&GameLevel::health, // health calculation
currentLevel, // member function;
_1) // see below for details
);
为了计算ebg2的健康指数,应该使用GameLevel class的成员函数health。GameLevel::haelth宣称它自己接受一个参数(那是个引用指向GameCharacter),但它实际上接受两个参数,因为它也获得了一个隐式参数gameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数只接受单一参数:GameCharacter(这个对象将被计算出健康指数)。如果我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个参数(一个GameCharacter和一个GameLevel),转而接受单一参数(一个GameCharacter)。这个例子中我们必然会想要使用currentLevel作为“ebg2的健康计算函数所需的那个GameLevel对象”,于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为:它指出ebg2的健康计算函数总是以currentLevel作为GameLevel对象。
传统Strategy模式
class GameCharacter; // forward declaration
class HealthCalcFunc {
public:
...
virtual int calc(const GameCharacter& gc) const
{ ... }
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public:
explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
: pHealthCalc(phcf )
{}
int healthValue() const
{ return pHealthCalc->calc(*this); }
...
private:
HealthCalcFunc *pHealthCalc;
};
这个设计方案是将virtual函数替换为另一个继承体系中的virtual函数。
effective c++:virtual函数的替代方案的更多相关文章
- Effective C++ -----条款35:考虑virtual函数以外的其他选择
virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...
- Effective C++:条款35:考虑virtual函数以外的其它选择
游戏中的人物伤害值计算问题. (一)方法(1):一般来讲能够使用虚函数的方法: class GameCharacter { public: virtual int healthValue() cons ...
- 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择
举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...
- 条款35:考虑virtual函数以外的其他选择
有一部分人总是主张virtual函数几乎总应该是private:例如下面这个例子,例子时候游戏,游戏里面的任务都拥有健康值这一属性: class GameCharacter{ public: int ...
- 条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)
NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函 ...
- Effective C++(9) 构造函数调用virtual函数会发生什么
问题聚焦: 不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果. 让我先来看一下在构造函数里调用一个virtual函数会发生什么结果 Demo class Trans ...
- Effective C++ -----条款09:绝不在构造和析构过程中调用virtual函数
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).
- effective c++:virtual函数在构造函数和析构函数中的注意事项
如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符. class Empty { p ...
- Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: ...
随机推荐
- POJ2282:The Counting Problem(数位DP)
Description Given two integers a and b, we write the numbers between a and b, inclusive, in a list. ...
- (四)动态生成控件,点击button添加控件
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- make clean、make mrproer、make distclean
make clean.make mrproer 以及make distclean的区别 解压内核源码包后, 到内核源代码目录树的顶层目录, 执行# make helpCleaning targets: ...
- 《OD学Oozie》20160807Oozie
一.引入 MapReduce Job Hive 脚本任务 同一个业务:先后.定时调度 工作流: 定义工作流程 activity jbpm oozie: 大数据工作流定义与调度框架 专门定义与调度Map ...
- listview android:cacheColorHint,android:listSelector属性作用
ListView是常用的显示控件,默认背景是和系统窗口一样的透明色,如果给ListView加上背景图片,或者背景颜色时,滚动时listView会黑掉, 原因是,滚动时,列表里面的view重绘时,用 ...
- 函数mem_pool_create
/********************************************************************//** Creates a memory pool. @re ...
- Machine Learning for hackers读书笔记(七)优化:密码破译
#凯撒密码:将每一个字母替换为字母表中下一位字母,比如a变成b. english.letters <- c('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ...
- 1038: [ZJOI2008]瞭望塔
半平面交. 半平面指的就是一条直线的左面(也不知道对不对) 半平面交就是指很多半平面的公共部分. 这道题的解一定在各条直线的半平面交中. 而且瞭望塔只可能在各个点或者半平面交折线的拐点处. 求出半平面 ...
- core--线程同步
[同步(tóng bù)synchronous;sync;synchronism;synchronization 指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系.]这是百度百科对&quo ...
- jQuery实例-记住登录信息
本文介绍下jquery 记住登录信息的方法,引入jquery.cookie.js文件,实现记住登录信息,有需要的朋友参考下. 首先,导入jquery.cookie.js $(function(){ / ...