如果类继承中重新定义了虚函数,那么虚函数中的缺省参数不要重新定义。
用一句话来解释原因就是:虚函数是动态绑定的(dynamically bound),但是缺省参数却是静态绑定的(statically bound)。
静态类型和动态类型
首先需要了解什么是对象的静态类型和动态类型,对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型。
以下面的类为例:
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: //当以对象调用这个函数时,一定要指定参数值。 //因为静态绑定下这个函数并不从其base继承缺省的参数值 //但若以指针或引用调用此函数,可以不指定参数值, //因为动态绑定下这个函数会从其base继承缺省参数值 virtual void draw(ShapeColor) const; };
考虑下面这些指针:
Shape *ps;//静态类型是Shape* Shape *pc=new Circle;//静态类型是Shape* Shape *pr=new Rectangle;//静态类型是Shape*
ps,pc,pr都被声明为point to Shape类型,所以他们都以它为静态类型。
对象的所谓动态类型(dynamic type)则是指它“目前所指向对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。以上例,pc的动态类型是Circle *,pr的动态类型是Rectangle *。ps没有动态类型,因为它尚未指向任何对象。
动态类型和它的名字所示,可以在程序执行过程中改变(通过赋值操作符)
pc=pr;//这条语句把pc的动态类型由Circle*改成了Rectangle*
个人感觉C++中的静态类型和动态类型和C中指针的类型(静态类型)和指针所指向数据的类型(动态类型)是类似的。
虚函数动态绑定和缺省参数静态绑定
静态绑定又叫前期绑定,early bounding;动态绑定又叫后期绑定,late binding。
虚函数系动态绑定而来,意思是调用一个虚函数时,究竟调用那一份函数实现代码,取决于发出调用的那个对象的动态类型。
如果函数的实现如下:
void Rectangle::draw(ShapeColor color) const{ switch (color) { case Green: { std::cout<<"rectangle draw green"<<std::endl; break; } case Red: { std::cout<<"rectangle draw red"<<std::endl; break; } default: break; } } void Circle::draw(ShapeColor color) const{ switch (color) { case Green: { std::cout<<"circle draw green"<<std::endl; break; } case Red: { std::cout<<"circle draw red"<<std::endl; break; } default: break; } }
那么pc和pr分别调用draw时,是根据pc和pr的动态类型Circle *和Rectangle *确定调用那个draw函数:
pc->draw(Shape::Red); pr->draw(Shape::Red);
但是如果不为上面的draw函数指定参数而使用缺省的参数,就又会有问题,因为虚函数是动态绑定的,而缺省函数是静态绑定的。结果就是调用一个定义于继承类中的虚函数但却使用的是基类中的缺省值。
如下:
pr->draw();
pc的动态类型是Rectangle *,它的虚函数draw中重新定义了缺省参数值是Green,但是通过上面的函数调用的结果如下:也就是说它调用的是Rectangle类的draw函数,但是使用的是Shape类的缺省值。这是由于pc的静态类型是Shape,而虚函数中的默认参数是静态绑定,所以默认参数是Shape中定义的。
C++中采用虚函数动态绑定,缺省参数静态绑定的原因在于运行期效率。如果缺省参数值是动态绑定,编译器就必须有某种方法在运行期为虚函数决定适当的参数缺省值。这比目前实行的在编译期决定的机制更慢而且更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。
上面还有一个问题就是Circle类中的draw没有定义默认参数,那么可能想的就是这种写法应该是正确的,它继承父类中的默认参数,但是实际上不完全是如此的。如果是以指针的形式调用这个函数,它的工作方式和想象中是一样的(即如何没有给draw指定实参,那么使用base类的缺省参数),但是如果以对象的形式使用这个函数,它在编译时就会报错
Circle oc; oc.draw();
在g++中编译错误如下:
当以对象调用这个函数时,一定要指定参数值。因为静态绑定下这个函数并不从其base继承缺省的参数值但若以指针或引用调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值。
那么如果在父类的虚函数中指定有缺省参数,子类如果正好要重写这个虚函数,造成的结果就是只能把缺省的参数复制一遍,并且不能修改(如上),但这样会造成大量的重复代码。
为了解决上面的问题,需要采用下面的方法
解决虚函数缺省参数值的方法
让它表现正常的做法就是采用使用NVI(non-virutual interface)手法:令基类中的public non-virtual函数调用private virtual函数,后者可被derived class重新定义。这里可以让non-virtual函数指定缺省参数,而private virtual函数负责真正的工作:
class Shape { public: enum ShapeColor{Red,Green,Blue}; void paint(ShapeColor color=Red) const { doPaint(color);//调用一个virtual函数 } private: virtual void doPaint(ShapeColor color) const =0; }; class Rectangle :public Shape { private: virtual void doPaint(ShapeColor color) const ; }; class Circle:public Shape { private: virtual void doPaint(ShapeColor color) const ; };
由于non-virtual函数绝不应该被重写,这样使得paint函数的color缺省值总是Red,解决了上面的问题。
完整的代码如下:
#include <stdlib.h> #include <stdio.h> #include <iostream> //对象的静态类型(static type),就是它在程序中被声明时所采用的类型 class Shape { public: enum ShapeColor{Red,Green,Blue}; virtual void draw(ShapeColor color=Red) const =0; void paint(ShapeColor color=Red) const { doPaint(color);//调用一个virtual函数 } private: virtual void doPaint(ShapeColor color) const =0; }; class Rectangle :public Shape { public: virtual void draw(ShapeColor color=Green) const; private: virtual void doPaint(ShapeColor color) const ; }; class Circle:public Shape { public: //当以对象调用这个函数时,一定要指定参数值。 //因为静态绑定下这个函数并不从其base继承缺省的参数值 //但若以指针或引用调用此函数,可以不指定参数值, //因为动态绑定下这个函数会从其base继承缺省参数值 virtual void draw(ShapeColor) const; private: virtual void doPaint(ShapeColor color) const ; }; void Rectangle::draw(ShapeColor color) const{ switch (color) { case Green: { std::cout<<"rectangle draw green"<<std::endl; break; } case Red: { std::cout<<"rectangle draw red"<<std::endl; break; } default: break; } } void Circle::draw(ShapeColor color) const{ switch (color) { case Green: { std::cout<<"circle draw green"<<std::endl; break; } case Red: { std::cout<<"circle draw red"<<std::endl; break; } default: break; } } void Rectangle::doPaint(ShapeColor color) const{ std::cout<<"rectangle doPaint "<<color<<std::endl; } void Circle::doPaint(ShapeColor color) const{ std::cout<<"circle doPaint "<<color<<std::endl; } int main() { Shape *ps;//静态类型是Shape* Shape *pc=new Circle;//静态类型是Shape* Shape *pr=new Rectangle;//静态类型是Shape* pr->paint(); // pc->draw(Shape::Red); // pr->draw(); // pr->draw(); // Circle oc; // oc.draw(); return 0; }