绝不重新定义继承来的缺省值

首先明确下,虚函数是动态绑定,缺省参数值是静态绑定

// 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函数的替代方案的更多相关文章

  1. Effective C++ -----条款35:考虑virtual函数以外的其他选择

    virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...

  2. Effective C++:条款35:考虑virtual函数以外的其它选择

    游戏中的人物伤害值计算问题. (一)方法(1):一般来讲能够使用虚函数的方法: class GameCharacter { public: virtual int healthValue() cons ...

  3. 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...

  4. 条款35:考虑virtual函数以外的其他选择

    有一部分人总是主张virtual函数几乎总应该是private:例如下面这个例子,例子时候游戏,游戏里面的任务都拥有健康值这一属性: class GameCharacter{ public: int ...

  5. 条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)

    NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函 ...

  6. Effective C++(9) 构造函数调用virtual函数会发生什么

    问题聚焦: 不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果. 让我先来看一下在构造函数里调用一个virtual函数会发生什么结果 Demo class Trans ...

  7. Effective C++ -----条款09:绝不在构造和析构过程中调用virtual函数

    在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).

  8. effective c++:virtual函数在构造函数和析构函数中的注意事项

    如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符. class Empty { p ...

  9. Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: ...

随机推荐

  1. 操刀 requirejs,自己动手写一个

    前沿 写在文章的最前面 这篇文章讲的是,我怎么去写一个 requirejs . 去 github 上fork一下,顺便star~ requirejs,众所周知,是一个非常出名的js模块化工具,可以让你 ...

  2. poj2265

    斜坐标系,注意找规律.说明在代码里. e- ?(x):-(x))<eps) ][] = { { ,  }, { -,  }, { -,  }, { , - }, { , - }, { ,  }  ...

  3. 截取usb数据包,控制usb设备----Relay设备

    在项目开发当中,我们需要一个usb转继电器的设备当开关控制无线发射设备,采购部采购时并未详细了解Relay设备的运行环境就买了一批设备,之后发现设备厂家只提供了windows库,而我们是要在linux ...

  4. C and C++ : Partial initialization of automatic structure

    Refer to: http://stackoverflow.com/questions/10828294/c-and-c-partial-initialization-of-automatic-st ...

  5. 自定义Eclipse的 “宏命令”

    在Eclipse中有些常用的固定的东西可以定义一个“宏命令”,就不用重复地做一些工作.就像在 Preferences > Java > Code Style > Code Templ ...

  6. 《c程序设计语言》读书笔记--字符串比较

    举例如下: char a[10]; 1.定义的时候直接用字符串赋值 char a[10]="hello"; 注意:不能先定义再给它赋值,如  char a[10];  a[10]= ...

  7. ERP

    企业资源计划即 ERP (Enterprise Resource Planning),由美国 Gartner Group 公司于1990年提出.企业资源计划是 MRP II(企业制造资源计划)下一代的 ...

  8. Android uiautomator gradle build system

    This will guide you through the steps to write your first uiautomator test using gradle as it build ...

  9. HNOI2008题目总结

    呜呼..NOI前一个月正式开始切BZOJ了……以后的题解可能不会像之前的零散风格了,一套题我会集中起来发,遇到一些需要展开总结的东西我会另开文章详细介绍. 用了一天的时间把HNOI2008这套题切了… ...

  10. ACM - ICPC World Finals 2013 F Low Power

    原题下载:http://icpc.baylor.edu/download/worldfinals/problems/icpc2013.pdf 题目翻译: 问题描述 有n个机器,每个机器有2个芯片,每个 ...