六、继承与面向对象设计
条款37、绝不重新定义继承而来的缺省参数值
- 本条款主要讨论:继承一个带有缺省参数的virtual函数
- 本条款成立的依据: virtual函数 是 动态绑定,缺省参数值 是 静态绑定
静态绑定 与 动态绑定
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 *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
现在,Rectangle类 与 Circle类 都继承自 Shape类。
ps、pc、pr 在声明时,是指向Shape的指针,所以,静态类型都是 Shape*
但 pc、pr 指向了两个类,所以它们动态类型分别为 Circle* 与 Rectangle* (ps未指向任何对象,所以没有动态类型)
pc->draw(ShapeColor::Red);
pr->draw(ShapeColor::Red);
分别相当于调用:
Circle::draw(ShapeColor::Red)
Rectangle::draw(ShapeColor::Red)
但,如果是
pr->draw();
理论上来讲,pr的动态类型是Rectangle* ,所以应该是 Rectangle::draw(ShapeColor::Green), 但由于 pr 的静态类型是Shape*, 它缺省参数值来自Shape class,也就是 ShapeColor::Red。
原因 与 错误的方法
C++ 这么做是为了节省执行效率,因为当 缺省参数值是动态绑定时,编译器就需要在运行期为virtual函数决定适当的参数缺省值,这无疑会复杂且低效。
如果我们提供缺省的参数值给 派生类及基类,又如何?
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;
...
};
—— 代码重复,而且不易修改维护
替换它, NVI手法
当然,对于这种需求,也非束手无策。
既然这条路行不通,那我们换条路,用别的替代它。
在条款35中就有许多替代方法,这里讲述 NVI手法
让基类的public non-virtual函数 调用 private virtual函数,让 non-virtual函数负责指定缺省参数值,virtual函数负责实现具体的东西。
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const
{
doDraw(color);
}
...
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle : public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const;
...
};
因为 non-virtual函数 绝对不应该被重定义(根据条款36),所以draw函数的 color缺省值,应该永远为ShapeColor::Red
请记住
- 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数(唯一应该被重定义的东西)是动态绑定。
注释:
- ①. 这样定义以后,如果用户通过对象调用此函数,一定要指定参数值。因为,静态绑定下这个函数并不会继承基类的缺省参数值。但如果通过指针(或引用)来调用这个函数,就可以不指定参数值,因为动态绑定下这个函数会继承基类的缺省参数值。