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 = 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函数。

你可能感兴趣的:(effective)