首页 > 代码库 > Effective C++:条款37:绝不又一次定义继承而来的缺省參数值

Effective C++:条款37:绝不又一次定义继承而来的缺省參数值

因为又一次定义继承而来的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函数-你唯一应该覆写的东西-却是动态绑定。