effective C++: 6.继承与面向对象设计(四)

2014-11-24 03:15:38 · 作者: · 浏览: 2
);//函数
EyeCandyCharacter ecc1(HealthCalculator());//函数对象
GameLevel currentLevel;
...
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));//成员函数
GameLevel::health宣称它接受两个参数,但实际上接受两个参数,因为它也获得一个隐式参数GameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数只接受单一参数:GameCharacter。如果我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个参数(一个GameCharacter和一个GameLevel),转而接受单一参数(GameCharacter)。于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为。


4.古典的Strategy模式
将健康计算函数做成一个分离的继承体系中的virtual成员函数。
class GameCharacter;
class HealthCalcFunc {
...
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;
};
每一个GameCharacter对象都内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。


请记住:
virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。




条款36:绝不重新定义继承而来的non-virtual函数

请记住:
绝对不要重新定义继承而来的non-virtual函数。




条款37:绝不重新定义继承而来的缺省参数值


绝不重新定义继承而来的缺省参数值,它有两层意思:
1.如果函数是非虚函数,你根本不应该重新定义它,因此也谈不上改变缺省参数值了。
2.如果函数是虚函数,由于缺省参数值是“静态绑定”,即使使用基类类型的指针或引用指向派生类函数,并且函数以派生类所定义的版本动作,其缺少参数值仍旧是基类所定义的!C++使用这种方式是为了“运行期效率”,在编译期确定参数比在运行期确定效率要高。
对象的所谓静态类型,是它在程序中被声明时所采用的类型。
class Shape {
public:
enum ShapeColor {Red, Green, Blue};
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape {
public:
//赋予不同的缺省参数。这真糟糕!
virtual void draw(ShapeColor color = Green) const;
};
class Circle: public Shape {
public:
virtual void draw(ShapeColor color) const;
//以上这么写当客户以对象调用此函数,一定要指定参数值
//因为静态绑定下这个函数并不从其base继承缺省参数值
//但若以指针(或reference)调用此函数,可以不指定参数值
//因为动态绑定下这个函数会从其base继承缺省参数值
};
Shape* ps; //静态类型是Shape*
Shape* pc = new Circle; //静态类型是Shape*
Shape* pr = new Rectangle; //静态类型是Shape*
ps,pc,pr不论这些指针指向什么,它们的静态类型都是Shape*
对象的所谓动态类型则是指“目前所指对象的类型”。可以在程序执行过程中改变
ps = pr;
ps = pc;
virtual 函数系动态绑定而来,意思是调用一个virtual 函数时, 究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型:
pc->draw(Shape::Red) //调用Circle::draw
pr->draw(Shape::Red) //调用Rectangle::draw
考虑带有缺省参数的virtual函数时,因为virtual函数是动态绑定的,而缺省参数是静态绑定的。意思是你可能在“调用一个定义于derived class内的virtual函数”的同时,却使用base class 为他所指定的缺省参数值:
pr->draw(); //调用Rectangle::draw(Shape::Red)!
pr的动态类型是Rectangle*,所以调用的是Rectangle的virtual函数。Rectangle::draw函数的缺省参数应该是Green,但由于pr的静态类型是Shape*,所以调用的缺省参数值来自Shape class而非Rectangle class!
即使把指针换成reference,问题仍然存在。

如果你遵守这条规则,并且同时提供缺省参数值给base和derived classes的用户,又会发生什么呢
class Shape {
public:
enum ShapeColor{Red, Green, Blue};
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape {
public:
virtual void draw(ShapeColor color = Red) const;
};
代码重复。更糟的是,又带有相依性;如果Shape内的缺省参数值改变了,所有“重复给定缺省参数值”的那些derived classes也必须改变,否则它们最终导致“重复定义一个继承而来的缺省参数值”。


解决办法:
当你想令virtual函数表现你所想要的行为却遭遇麻烦,聪明的做法是考虑替代设计。条款35。其中之一就是NVI手法:
令base class内的一个public non-virtual函数调用一个private virtual函数,后者可被derived classes重新定义。这里我