- 静态绑定与动态绑定的差异:虚拟函数是动态绑定,即调用的特定函数取决于对象的动态类型;而缺省参数值是静态绑定,取决于对象的静态类型。例如:
// a class for geometric shapes
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
// all shapes must offer a function to draw themselves
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape {
public:
// notice the different default parameter value — bad!
virtual void draw(ShapeColor color = Green) const;
};
class Circle : public Shape {
public:
virtual void draw(ShapeColor color) const;
};
Shape *ps; // static type = Shape*
Shape *pc = new Circle; // static type = Shape*
Shape *pr = new Rectangle; // static type = Shape*
pc->draw(Shape::Red); // calls Circle::draw(Shape::Red)
pr->draw(Shape::Red); // calls Rectangle::draw(Shape::Red)
pr->draw(); // calls Rectangle::draw(Shape::Red)! 调用Rectangle的draw函数,但使用了Shape的缺省参数值Red
在上述代码中,pr
的动态类型是 Rectangle*
,静态类型是 Shape*
,当调用 pr->draw()
时,虽然调用的是 Rectangle
的 draw
函数,但由于缺省参数值是静态绑定,使用的是 Shape
类中的缺省参数值 Red
,而不是 Rectangle
类中重定义的 Green
,这可能导致意外结果。
C++ 如此设计的原因:为了运行时效率,若缺省参数值动态绑定,编译器需在运行时确定其值,这比在编译期确定更慢且复杂。
避免代码重复的方法:若在基类和派生类中提供相同的缺省参数值会导致代码重复,可考虑使用非虚拟接口惯用法(NVI idiom)。例如:
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const // now non - virtual
{
doDraw(color); // calls a virtual
}
private:
virtual void doDraw(ShapeColor color) const = 0; // the actual work is
};
class Rectangle : public Shape {
private:
virtual void doDraw(ShapeColor color) const;
};
在这种设计中,通过基类中的公有非虚拟函数 draw
指定缺省参数,调用派生类可能重定义的私有虚拟函数 doDraw
做实际工作,明确了 draw
的 color
参数的缺省值永远是 Red
。
总之,由于缺省参数值的静态绑定和虚拟函数的动态绑定特性,在继承体系中重定义继承的缺省参数值会导致不符合预期的行为,应避免这种做法,可采用合适的替代设计如 NVI idiom。