由于重新定义继承而来的non-virtual函数是不正确的(见上一个条款),所以这个条款就将问题局限于:绝不重新定义继承一个带有缺省参数值的virtual函数。
(一)
virtual函数是动态绑定的,而缺省参数却是静态绑定。
对象的所谓静态类型,是它在程序中被声明时所采用的类型。
你可能会在“调用一个定义于derived class 内的virtual函数”的同时,却使用了base class为它所指定的缺省参数值。
(二)
为什么继承而来的virtual函数的缺省参数值不能被重新定义呢?
其实原因也挺简单:缺省参数是静态绑定,而virtual函数是动态绑定. 所谓对象的静态绑定也叫前期绑定,它是说该对象类型和行为在程序编译期间就可以确定,例如:
class Shape{ public: enum Color{RED,GREEN,BLUE}; virtual void draw(Color color = RED)const = 0; ... }; class Circle : public Shape{ public: //哦欧! 竟然改变缺省参数值 virtual void draw(Color color = GREEN)const{ ... } }; class Rectangle : public Shape{ public: //没用指定参数类型,需要用户去明确的指定其值 //静态绑定下不继承基类的缺省值,若以指针或引用调用则不需要指定缺省值,因为动态绑定 //继承基类的参数缺省值 virtual void draw(Color color)const{ ... } };看一下下面几个指针:
Shape* ps; Shape* pc = new Circle; Shape* pr = new Rectangle;这里的ps,pc,pr不管它具体指向的是什么对象,他们的静态类型都是Shape*。而动态类型就是它们真正指向的对象的类型。故pc的动态类型为Circle*,而pr的动态类型为Rectangle*,ps由于没有指向任何对象,所以此时没有动态类型。
(三)看下面这个语句!
pc->draw(); //注意调用的是: Circle::draw(RED)怎么会调用Circle::draw(RED)呢!?为什么不是Circle::draw(GREEN)?
原因:
(1)首先根据其调用语句用指针这一事实,我们就知道了其调用的版本应该是该指针的动态类型的函数版本,即Circle::draw,这个问题不大。
(2)下面我们来看它的传值参数,前面我们提到缺省参数值是静态绑定的,而pc的静态类型是Shape*,所以该参数的传入值是Shape的该函数版本的缺省值。
那为什么C++坚持以这种乖张的方式来运作呢?答案在于运行期效率,如果缺省值也是动态绑定的,那么编译期就必须要有办法在运行期为virtual函数决定适当的参数缺省值.如果这样做的话,就要比目前实现的"在编译期决定"的机制更慢而且更复杂,考虑到执行速度和实现上的简易性,C++放弃了这样的做法。
(四)解决方法!
现在,为了遵循本款约定却同时提供缺省参数值给你的基类和父类,,代码就这样了:
class Shape{ public: enum Color{RED,GREEN,BLUE}; virtual void draw(Color color = RED)const = 0; ... }; class Circle:public Shape{ public: virtual void draw(Color color = RED)const {...} };明显的是代码重复嘛!何况你要是想改变缺省值的话,必须要同时改变基类和子类函数的缺省值,一不小心,就会出现漏改或写错的情况,导致意想不到的错误出现.有没用一种更方便的写法呢?当然,你还记得NVI手法吗?额..,(non-virtual interface),要是忘记的话,回过头看看条款35,用这种手法的话,我们写下代码如下:
class Shape{ public: enum Color{RED,GREEN,BLUE}; void draw(Color color = RED) const{ ... doDraw(color); ... } ... private: virtual void doDraw(Color color) const = 0; }; class Circle:public Shape{ ... private: virtual void doDraw(Color color){ ... } };由于draw是non-virtual而non-virtual绝对不会被重新改写(条款36),所以color的缺省值总是为RED。
请记住:
(1)绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定。