原因
让我们一开始就将讨论简化。你只能继承两种函数,virtual和non-virtual函数。然而重新定义一个继承而来的non-virtual函数永远是错误的。我们可以安全的将本条款的讨论局限于”继承一个带有缺省参数的virtual函数“。
这种情况下,本条例成立的理由就十分明确了:virtual函数是动态绑定,而缺省参数值是静态绑定。
解释
对象的所谓静态类型,就是它在程序中被声明时所采用的类型。看个例子:
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)
};
class Circle : public Shape{
public:
virtual void draw(ShapeColor color) const = 0;
// 注意:上面这么写则当客户以对象调用此函数,一定要指定参数值
// 因为静态绑定下这个函数并不从其Base继承缺省参数值
// 但是如果以指针或引用调用此函数,可以不执行参数值
// 因为动态绑定下这个函数会从其基类继承缺省参数值
}
现在考虑这些指针:
Shape *ps ; // 静态类型为Shape*
Shape *pc = new Circle; // 静态类型为Shape*
Shape *pr = new Rectangle ; // 静态类型为Shape*
对象的所谓动态类型则是值目前所指对象的类型。也就是说,动态类型可以表现出一个对象将有什么行为。以上面为例,ps没有动态类型,因为它尚未指向任何对象;pc的动态类型时Circle*, ps的动态类型时Rectangle *
动态类型如名所示,可以在程序执行过程中改变(通过通过赋值)
ps = pc; //ps的动态类型变为Circle*
ps = pr; //ps的动态类型变为Rectangle *
虚函数是动态绑定的,意思是调用一个虚函数时,究竟调用拿一份函数实现代码,取决于发出调用的那个对象的动态类型:
pc->draw(Shape::Red); //调用Circle::draw(Shape::Red);
pr->draw(Shape::Red); //调用Rectangle ::draw(Shape::Red);
那么如果是带有缺省参数的虚函数---- 可能”调用一个定义于派生类内的虚函数,但是使用基类为它所指定的缺省参数值“的后果:
pr->draw(); // Rectangle ::draw(Shape::Red);
上面的预期缺省值应该是GREEN,但是由于pr的静态类型是Shape*,所以此时调用的缺省参数值是Shape class的而不是Rectangle class的。
引用所面对的问题和指针一样。重点在于draw是个虚函数,而它有个缺省参数在派生类中被重新定义了。
为什么C++坚持以这种方式运作呢?答案在于运行期效率。如果缺省参数是动态绑定,编译器就必须有某种方法在运行期为虚函数决定适当的参数缺省值。这会造成效率低下而且实现复杂。
那么如果你遵守这条规则,并且同时提供缺省值给基类和派生类,又会发生什么呢?
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=Red)
};
这很糟糕:代码重复,而且如果Shape内的缺省参数改变了,那些派生类也要跟着改。怎么办呢?
当你想让虚函数表现出你想要的行为却遭遇麻烦,这时候C/C++编程:考虑虚函数以外的其他选择 。比如NVI手法(模板方法模式):令基类的一个public non-virtual函数调用private virtual函数。这里我们可以让non-virtual函数指定缺省值参数,而private virtual函数负责真正的工作:
class Shape{
public:
enum ShapeColor{Red, Green, Blue};
void draw(ShapeColor color=Red){
doDraw(color);
}
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle : public Shape{
private:
virtual void doDraw(ShapeColor color) const;
};