4 考虑virtual函数之外的选择
考虑为游戏内的人物设计一个继承体系。
class GameCharacter {
public:
virtual int healthValue() const; // 返回人物的健康指数。
......
};
有时候,常规的面向对象设计方法往往看起来是那么的自然,以至于我们从未考虑其他的一些解法。
这一节就让我们跳出常规设计的思维,考虑一些不那么常规的设计方法。
方法1:借由non-virtual interface手法实现Template Method模式
class GameCharacter {
public:
int healthValue() const {
...
int retVal = doHealthValue();
...
return retVal;
}
....
private:
virtual int doHealthValue() const {
...
}
};
让客户通过public non-virtual成员函数间接调用private virtual函数,称为non-virtual interface(NVI)手法。
这个non-virtual函数(healthValue)称为virtual函数的包装器(wrapper)。
从程序执行的角度来看,derived classes重新定义了virtual函数,从而赋予它们“如何实现功能”的控制能力,base classes保留控制“函数何时被调用”的权利。
方法2:借由Function Pointer实现Strategy模式
代码如下:
class GameCharacter:;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef int ( *HealthCalcFunc ) ( const GameCharacter& );
explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc ) : healthFunc(hcf) { }
int healthValue() const {
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};
还有其他的一些方法,在此并不一一讨论,详见《Effective C++》
5 绝不重新定义继承而来的non-virtual函数
在子类中重定义继承而来的non-virtual函数,会导致子类你的设计出现矛盾。
比如在class Base有一个non-virtual函数setBigger,而所有继承Base的子类都可以执行变大的动作,那么这个动作就是一个不变性(共性)。
而在class Derived : public Base子类中,重写了setBigger函数,那么class Derived便无法反映出“不变性凌驾于特性”的性质。
从另一方面说,如果setBigger操作真的需要在子类中重定义,那么就不应该把它设定为一个共性(non-virtual)。
因此,重新定义继承来的non-virtual函数可能并不会对你的程序的运行造成太大的困扰,但是正如上面提到的,这是设计上的矛盾,或者说缺陷。
6 绝不重新定义继承而来的缺省参数值
本小节的讨论局限于“继承一个带有缺省参数值的virtual函数”。
理由:virtual函数动态绑定,缺省参数值静态绑定。
class Shape {
public:
Shape() {};
enum ShapeColor { Red = "red", Green = "green" , Blue = "blue"};
virtual void draw(ShapeColor color = Red) const = 0
{
std::cout << "This shape is " << color << std::endl;
}
};
class Rectangle : public Shape {
public:
Rectangle() {};
virtual void draw ( ShapeColor color = Green ) const;
};
class Circle : public Shape {
public:
virtual void draw(ShapeColor color) const;
};
先考虑如下指针
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;
ps、pc、pr的静态类型都是Shape*
所谓动态类型就是“目前所指对象的类型”。也就是说动态类型可以表现出一个对象将会有什么行为。
在本例中,ps没有动态类型,pc的动态类型为Circle*,pr的动态类型为Rectangle*。
动态类型可以在程序执行过程中改变(通常是经由赋值动作)。如
ps = pc; // ps的动态类型现在是Circle
ps = pr; // ps的动态类型现在是Rectangle
上面是对动态绑定和静态绑定的简单复习。
现在,考虑带有缺省参数值的virtual函数。
在上面的例子中,Shape中的draw函数的color默认参数是Red,而子类中的draw函数的color默认参数是Green。
Shape* shape = new Rectangle();
shape->draw();
根据动态绑定规则,上述代码的输出应该为:This shape is 1
但是运行代码之后会发现,结果并不是我们想的那样。

我们来分析一下导致这种结果的原因:
shape的动态类型为Rectangle*,调用draw时,根据动态绑定,调用的应该为Rectangle的版本
Rectangle版本的draw的默认参数应该为Green(1)
而结果是Red(0)
为了更清楚的看一下究竟调用的是哪一个draw,我们多加一点打印信息。

ok,结果很清楚了,函数调用的版本是Rectangle*,但是默认参数调用的是Shape*中定义的。
所以结论就是:这个函数调用由class Shape和class Rectangle class的draw声明式各出一半力。
那么C++为什么有这么奇怪的设定呢 简单的说,就是:性能。运行期对参数动态绑定缺省值很慢很复杂,所以考虑到性能问题,并没有支持默认参数的动态绑定。
小结:
所以正如本节的标题,禁止重载一个继承而来的默认参数值,因为缺省参数值都是静态绑定,而使用这些默认参数的virtual函数却是动态绑定。