前言
在条款36介绍过,在继承体系中,派生类最好只重写覆盖virtual函数,而不要去隐藏基类的non-virtual函数 因此,本条款要介绍的“不要重新定义继承而来的缺省参数值”,是针对于virtual函数而言的 一个重要的概念: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 = 0;
};
class Circle :public Shape {
public:
virtual void draw(ShapeColor color)const = 0;
};
现在我们定义下面的代码,它们都被声明为pinter-to-Shpae类型,因此它们不论它们指向什么,静态类型都是Shape*:
Shape* ps; //静态类型为Shape*
Shape* pc = new Circle; //静态类型为Shape*
Shape* pr = new Rectangle; //静态类型为Shape*
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;
ps = pc; //ps的动态类型如今是Circle*
ps = pr; //ps的动态类型如今是Rectangle*
根据语法我们知道,对于virtual函数的调用,是根据其动态类型决定的。例如:
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;
pc->draw(Shape::Red); //调用Circle::draw(Shape::Red)
pr->draw(Shape::Red); //调用Rectangle::draw(Shape::Red)
二、virtual函数的缺省参数值是静态绑定的
虽然对于virtual函数的调用时动态绑定的,但是对于virtual函数的缺省参数值却是静态绑定的 见下面的代码:
我们知道virtual函数是动态绑定的, pr的动态类型为Rectangle,所以调用的是Rectangle::draw()但是virtual函数的缺省参数值是静态绑定的, 在上面类的定义中Rectangle的draw()函数无参数,但是由于pr指针的静态类型是Shape,因此其draw()函数的缺省参数值就是Shape::draw()函数中的参数值,为Shape::Red
Shape* pr = new Rectangle;
pr->draw(); //调用的是Rectangle::draw(Shape::Red)
//Circle也是相同的道理
Shape* pc = new Circle;
pc->draw(); //调用的是Circle::draw(Shape::Red),而不是Circle::draw(Shape::Green)
为什么要设计这种行为:在于运行效率。 如果缺省参数值也是动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的参数缺省值,这比目前实行的“在编译期决定”的机制更慢而且更复杂
三、不要重新定义继承而来的缺省参数值
通过二,我们知道virtual函数的缺省参数值是静态绑定的。因此,我们不要重新定义继承而来的缺省参数值,因为这会在调用virtual函数时产生意想不到的效果(上面代码中,通过pc调用draw()就是一个例子)
四、针对于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 = Red)const;
};
class Circle :public Shape {
public:
virtual void draw(ShapeColor color = Red)const;
};
低效率的原因:
①代码重复 ②依赖性太高,如果基类中的缺省参数值改变了,那么需要将派生类中的缺省参数值都修改一遍
以NVI手法定义class
条款36介绍了NVI手法,对于virutal函数的缺省参数值,为了避免基类与派生类中的缺省参数值不一致,我们可以采取这种方法 定义的代码如下:
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red)const { //因为是non-virtual函数,因此不建议派生类隐藏
doDraw(Red);
}
private:
//真正的工作在此处完成,派生类可以重写
virtual void doDraw(ShapeColor color)const = 0;
};
class Rectangle :public Shape {
private:
virtual void doDraw(ShapeColor color)const = 0;
};
上面的doDraw()函数完成真正的功能,并且接受一个ShapeColor类型 我们又定义了一个non-virtual函数draw,其参数默认为Red,并且non-virtual函数不建议派生类隐藏,因此不论是基类还是派生类调用draw()函数,参数的默认值将永远是Red,达到了我们最终的目的
五、总结
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virutal函数——你唯一应该覆盖的东西——却是动态绑定