继承中虚函数的缺省参数值问题
如果类继承中重新定义了虚函数,那么虚函数中的缺省参数不要重新定义。
⽤⼀句话来解释原因就是:虚函数是动态绑定的(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
{
enum函数
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;