C++中virtual函数中的缺省参数值不应被派生类重新定义
C++中派生类从基类继承之后,non-virtual函数不应被重新定义,因为它会导致派生类对象的行为不一致性,也违背了public的"is a"继承关系。除了non-virtual函数不该被派生类重新定义外,基类virtual函数中的缺省参数值也不应该被派生类重新定义,其主要原因是virtual函数是动态绑定,而缺省参数值却是静态绑定。
(1)对象的动态类型和静态类型
对一个对象,其静态类型指其被声明时的类型,其动态类型则是指其所指的具体对象的类型。例如下面的继承体系:
1 class Shape 2 { 3 public: 4 enum ShapeColor { Green, Red, Blue }; 5 virtual void draw(ShapeColor color = Red) const = 0; 6 ... 7 }; 8 9 class Rectangle : public Shape 10 { 11 public: 12 virtual void draw(ShapeColor color = Green) const; 13 ... 14 }; 15 16 class Circle : public Shape 17 { 18 public: 19 virtual void draw(ShapeColor color) const; 20 ... 21 };
根据上面的继承体系,如果定义下面的变量:
1 Shape* ps; 2 Shape* pr = new Rectangle; 3 Shape* pc = new Circle;
那么,ps, pr, pc都是pointer-to-Shape,不论它们指向什么,它们的静态类型都是Shape*,而它们的动态类型则是指它们所指的对象的类型,也即动态类型可以表现出一个对象将会有什么行为,对此,pr,pc的动态类型分别是Rectange*, Circle*,ps则没有动态类型,因为它不指向任何对象。
动态类型在程序中可以改变,如:
1 ps = pc; 2 ps = pr;
Virtual函数是动态绑定的,意味着调用virtual函数时具体指哪一个取决于调用者的动态类型。
(2)virtual函数的缺省参数值是静态绑定的
virtual是动态绑定的,但是virtual的缺省参数值是静态绑定的,也就是说当你写下如下语句时:
pr->draw()
由于pr的动态类型是Rectangle,所以你调用的函数是Rectangle中的函数,此时缺省的参数值应该是Green,但实际上却是来自base class的Red,这与预期的操作是不符的——因为你想调用的是Rectangle的函数,自然也是想要使用它的缺省参数值,但事实却是来自基类的缺省参数值,其原因就是这个缺省参数值是静态绑定的,而pr的静态类型却是Shape*。
C++之所以这么做是因为将缺省参数值设为动态绑定要求编译器在运行时判断适当的缺省参数值,这比“编译期决定”更慢更复杂,为了程序的执行速度和编译器的简易度,C++选择了将缺省参数值设为静态绑定。
(3)替代设计
如果想要派生类能够修改缺省参数值,那么可以考虑替代设计,所谓的替代设计是用non-virtual函数替代virtual函数,同时将virtual函数声明为base class的private成员函数,具体实现如下:
1 class Shape 2 { 3 public: 4 enum ShapeColor { Red, Green, Blue }; 5 void draw(ShapeColor color = Red) const 6 { 7 doRealDraw(color); 8 } 9 private: 10 virtual void doRealDraw(ShapeColor color ) const = 0; 11 }; 12 13 class Rectangle : public Shape 14 { 15 public: 16 virtual void doRealDraw(ShapeColor color) const; 17 }
以上整理自Effective C++中文版第三版 case37.