loujiayu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

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

// 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 = 0;
  ...
};
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 = 0; // 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函数。

posted on 2014-04-01 22:06  Stinky tofu  阅读(288)  评论(0编辑  收藏  举报