现在考虑这样一种继承体系:
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;
//...
};
你希望缺省情况下,任何继承自 Shape
的类型了颜色为红色。但是同时呢,你在 Rectangle
类中对这个缺省的颜色进行了重定义,希望 Rectangle
的缺省颜色为绿色。然而事实真的如此吗?
考虑下面这种情况:
Shape *ps;
Shape *pc = new Circle();
Shape *pr = new Rectangle();
pc->draw();
pr->draw();
你会发现, pc->draw() 画出来的圆形是红色,pr->draw() 画出来的矩形,仍然是红色。这是因为:
virtual function 是动态绑定,而缺省参数值是静态绑定的。
静态绑定与动态绑定
对象的类型分为静态类型和动态类型。当然这里的对象只针对指针类型的对象,包括引用。
- 静态类型:就是对象在程序中被声明的类型
- 动态类型:对象目前所实际指向的类型
virtual 函数是动态绑定的,也就是说,当调用一个 virtual 函数时,究竟调用继承体系中哪一份代码,取决于发出调用的那个对象的动态类型;
缺省参数却是静态绑定的,也就是说,当调用一个函数缺省参数的函数时,调用的是发出调用的那个对象的静态类型。
因此,对于上面的 pr->draw();
由于 draw()
是 virtual function,所以它调用的是发出调用对象 pr
的动态类型 Rectangle
中的 draw()
函数,即 Rectangle::draw();
但是,对于缺省参数,由于 pr
的静态类型为 Shape
,所以获得的是 Shape::draw()
中的缺省参数 Red。
所以,不应该对继承而来的缺省参数值进行重新定义,否则它可能会导致非预期行为。
NVI 实现 virtual function
对于上面这种情景,应该利用 NVI 去实现 virtual function,放置子类去重写缺省参数。关于 NVI,可以参考这篇文章。
class Shape
{
public:
enum ShapeColor {Red, Green, Blue};
void draw(ShapeColor color = Red) const
{
doDraw(color);
}
//...
private:
virtual void doDraw(ShaperColor color) const;
//...
};
class Rectangle : public Shape
{
public:
//...
private:
virtual void doDraw(ShaperColor color) const;
};
class Circle : public Shape
{
public:
//...
private:
virtual void doDraw(ShaperColor color) const;
};