有个抽象类表示形状,其中纯虚函数draw有默认形参:
class shape{
public:
enum shapecolor{Red,Green,Blue};
virtual void draw(shapecolor col=Red) const=0;
std::string retcol(shapecolor col) const{
std::string str;
switch(col)
{
case Red:
str="Red";break;
case Green:
str="Green";break;
case Blue:
str="Blue";break;
default:
break;
}
return str;
}
};
同时有两个继承类表示长方形和圆形,分别提供了带默认形参和不带默认形参的draw实现:
class rectangle:public shape{
public:
virtual void draw(shapecolor col=Green) const{
cout<<"rectangle::draw "<<retcol(col)<<endl;
}
};
class circle:public shape{
public:
virtual void draw(shapecolor col) const{ //when user call draw,para have to be set
cout<<"circle::draw "<<retcol(col)<<endl;
}
};
实例化对象后调用draw函数:
rectangle rec;
rec.draw();
circle cir;
cir.draw(shape::Blue); //if no para,report err
shape* ptr=new rectangle();
ptr->draw();
shape* ptr1=new circle();
ptr1->draw();
结果为:
而通过base指针调用rectangle和circle对象的draw(),获取到的默认参数却都是Red。这说明默认参数是静态绑定的,由于指针是base类型,故编译期就已决定是base的默认参数,尽管调用哪个virtual函数时动态绑定的。于是导致了base类和derived类各出一半力的怪异结果。
由此可见绝不应该重新定义继承而来的参数值。那么derived类该如何指定默认形参呢?
也指定为Red?那会导致代码重复而且会对base类产生依赖。即base类一旦改变参数值derived也得跟着改。
较好方法是使用NVI手段将draw函数移到private,并提供一个public non-virtual 函数(外覆器)调用真正干活的函数dodraw:
class shape{
public:
enum shapecolor{Red,Green,Blue};
void draw(shapecolor col=Red) const{
dodraw(col);
}
std::string retcol(shapecolor col) const{
std::string str;
switch(col)
{
case Red:
str="Red";break;
case Green:
str="Green";break;
case Blue:
str="Blue";break;
default:
break;
}
return str;
}
private:
virtual void dodraw(shapecolor col) const=0;
};
class rectangle:public shape{
private:
virtual void dodraw(shapecolor col) const{
cout<<"rectangle::draw "<<retcol(col)<<endl;
}
};
class circle:public shape{
private:
virtual void dodraw(shapecolor col) const{
cout<<"circle::draw "<<retcol(col)<<endl;
}
};
可见
通过NVI手法仅在外覆器指定默认参数,因为non-virtual本来就不该被重新定义,否则属于名称覆盖和多态没关系了。
当然derived类也就不指定默认参数了。现在调用完全符合预期:
rectangle rec;
rec.draw();
circle cir;
shape* ptr=○
ptr->draw();
ptr->draw(shape::Green);