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: ...
随机推荐
- 《Java编程那点事儿》读书笔记(三)——static,this,抽象类,接口和包
1. static 1)静态变量:Java虚拟机为静态变量开辟单独的存储空间,所以所有的对象内部的静态变量在内存中都指向同一个地址,那么不管哪个对象改变这个成员变量,所有对象中该成员变量的值都发生变化 ...
- Spring高级事务管理难点剖析
1Spring事务传播行为 所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播.Spring支持7种事务传播行为 PROPAGATION_REQUIRED(加入已有事务) 如果当前没 ...
- make clean vs make clobber
make is pretty smart, and picks up what has changed from the last build, so if you run repo sync and ...
- BZOJ 1018 堵塞的交通traffic(线段树)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1018 题意:一个2*n的格子,相邻格子之间有一条道路.初始时道路是不通的. 三种操作:( ...
- leetcode:Add Two Numbers
题目描述:You are given two linked lists representing two non-negative numbers. The digits are stored in ...
- jQuery mouseover与mouseenter的区别
在我们的页面中经常会用到mouseover与mouseout事件来给页面添加更好的渲染效果,但如果触发mouseover事件的元素有子元素的话,会造成闪烁的效果,看着很不舒服,这是因为mouseove ...
- gitflow workflow
https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow Dream big, work smart, ...
- 如何在linux中从源代码编译安装nodejs?
$ sudo yum groupinstall 'Development Tools'安装开发环境$ wget https://nodejs.org/dist/v0.12.2/node-v0.12.2 ...
- UVa 821 Page Hopping【Floyd】
题意:给出一个n个点的有向图,任意两个点之间都相互到达,求任意两点间最短距离的平均值 因为n很小,所以可以用floyd 建立出图,然后用floyd,统计d[][]不为0且不为INF的边的和及条数,就可 ...
- PopupWindow+ListView
1. 获取打到数据 for (int i = 0; i < iocOutMakeMaterialSubmit.data.size(); i++) { dataListPopupWindow.ad ...