条款37:绝不重新定义继承而来的缺省参数值
在 C++ 中,缺省参数值 是 静态绑定 的(即编译时确定),而 virtual
函数 是 动态绑定 的(即运行时根据对象动态类型确定)。这两者的绑定方式不同,可能会导致预期之外的行为。
问题说明
当你在派生类中重新定义继承而来的缺省参数值时,实际调用的函数实现可能是派生类的,但缺省参数值仍然来自基类。这种混淆可能导致难以察觉的错误。
示例:重新定义缺省参数值
class Shape { public: enum ShapeColor { Red, Green, Blue }; virtual void draw(ShapeColor color = Red) const { doDraw(color); } private: virtual void doDraw(ShapeColor color) const = 0; }; class Rectangle : public Shape { public: virtual void draw(ShapeColor color = Green) const override { doDraw(color); } private: virtual void doDraw(ShapeColor color) const override { std::cout << "Drawing Rectangle with color " << color << std::endl; } }; int main() { Shape* shape = new Rectangle; shape->draw(); // 输出: Drawing Rectangle with color 0 (Red) delete shape; return 0; }
解析
在上例中:
- 调用
shape->draw()
时,由于draw
是virtual
函数,动态绑定生效,调用的是Rectangle::draw
。 - 但缺省参数值是静态绑定的,因此编译时,调用
shape->draw()
的缺省参数值来自Shape::draw
的定义,即Red
。 - 因此,
Rectangle::draw
使用了错误的缺省参数值Red
,而不是派生类的Green
。
解决方法
避免重新定义缺省参数值。可以通过以下方法重构代码以避免潜在问题:
方法 1:仅使用基类的缺省参数值
确保所有缺省参数值的定义只出现在基类中,而派生类仅提供 virtual
函数的具体实现。
class Shape { public: enum ShapeColor { Red, Green, Blue }; virtual void draw(ShapeColor color = Red) const { doDraw(color); } private: virtual void doDraw(ShapeColor color) const = 0; }; class Rectangle : public Shape { private: virtual void doDraw(ShapeColor color) const override { std::cout << "Drawing Rectangle with color " << color << std::endl; } }; int main() { Shape* shape = new Rectangle; shape->draw(); // 输出: Drawing Rectangle with color 0 (Red) delete shape; return 0; }
方法 2:通过非虚函数间接调用虚函数
使用 Non-Virtual Interface (NVI) 手法,将 draw
声明为非虚函数并提供缺省参数值,实际绘制操作由私有的 virtual
函数完成。
class Shape { public: enum ShapeColor { Red, Green, Blue }; void draw(ShapeColor color = Red) const { doDraw(color); } private: virtual void doDraw(ShapeColor color) const = 0; }; class Rectangle : public Shape { private: virtual void doDraw(ShapeColor color) const override { std::cout << "Drawing Rectangle with color " << color << std::endl; } }; int main() { Shape* shape = new Rectangle; shape->draw(); // 输出: Drawing Rectangle with color 0 (Red) delete shape; return 0; }
总结
- 缺省参数值 是静态绑定的,而
virtual
函数是动态绑定的,两者的绑定机制不同。 - 不要重新定义继承而来的缺省参数值,因为这会导致代码行为与预期不符。
- 为了避免问题,建议将缺省参数值的定义限制在基类中,或使用 NVI 手法封装动态行为。