### 决不要重新定义继承而来的缺省参数值
在C++编程中,遵循最佳实践对于维护代码质量和可预测性至关重要。本篇文章聚焦于“决不要重新定义继承而来的缺省参数值”的原则,该原则出自权威指南《Effective C++》。我们将深入探讨这一规则背后的原因、潜在的问题以及如何避免这些问题。
#### 什么是缺省参数?
在C++中,缺省参数允许你在函数定义中指定某些参数的默认值。这意味着当你调用函数时,如果没有明确传递这些参数的值,则会使用默认值。例如:
```cpp
void exampleFunction(int a, int b = 5);
```
在这个示例中,`b` 的缺省值为 `5`。
#### 继承与虚函数
在C++中,类可以通过继承共享属性和行为。当一个类继承另一个类时,它可以访问基类的所有公共和受保护成员。此外,还可以定义虚函数,以支持多态性。虚函数允许派生类重写或覆盖基类的行为。
#### 问题的核心
问题的核心在于虚函数和缺省参数值的绑定方式不同。虚函数是动态绑定的,这意味着当通过指针或引用调用虚函数时,实际调用的是对象动态类型所对应的版本。另一方面,缺省参数值是静态绑定的,它们在编译时就已经确定,不受对象动态类型的影响。
#### 为何不能重新定义缺省参数值?
假设有一个基类 `Shape` 和两个派生类 `Rectangle` 和 `Circle`。`Shape` 类有一个虚函数 `draw()`,它接受一个颜色参数,并为该参数设置了一个缺省值 `RED`。
```cpp
enum ShapeColor { RED, GREEN, BLUE };
class Shape {
public:
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:
virtual void draw(ShapeColor color) const;
};
```
这里,`Rectangle` 类尝试将 `draw()` 函数的缺省参数值从 `RED` 更改为 `GREEN`。虽然这看似合理,但实际上会导致问题。考虑以下代码:
```cpp
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;
pr->draw(); // 调用 Rectangle::draw() 但使用 Shape::draw() 的缺省参数值 RED!
```
这里的问题是,尽管 `pr` 指向一个 `Rectangle` 对象,但由于 `draw()` 是虚函数,因此调用的是 `Rectangle::draw()`。然而,由于缺省参数值是静态绑定的,因此使用的缺省参数值是 `Shape` 类中定义的 `RED` 而不是 `Rectangle` 类中定义的 `GREEN`。这种行为不仅令人困惑,而且可能导致意外的结果。
#### 为什么C++这样设计?
C++之所以采取这种设计决策,主要是出于性能和实现简单性的考虑。如果缺省参数值能够在运行时确定,那么编译器就需要在运行时进行额外的查找工作,以确定正确的缺省值,这将降低程序执行的速度并增加实现的复杂度。因此,为了保持较高的执行效率和简洁的实现,C++选择了静态绑定的方案。
#### 如何避免问题?
为了避免上述问题,最简单的做法就是遵循“决不要重新定义继承而来的缺省参数值”的原则。如果你确实需要更改缺省参数值,应该显式地为函数调用提供参数,或者在派生类中定义一个新的重载函数,而不改变原有虚函数的缺省参数值。
例如,可以在 `Rectangle` 类中定义一个新的 `draw()` 函数,显式地指定绿色作为缺省颜色:
```cpp
class Rectangle : public Shape {
public:
void drawGreen() const { draw(GREEN); }
};
```
这样,你可以通过调用 `drawGreen()` 来确保使用绿色作为缺省颜色,同时保留了原有的 `draw()` 函数及其缺省参数值。
#### 结论
通过理解虚函数和缺省参数的绑定机制,我们可以更好地遵循《Effective C++》中的这条建议,从而避免潜在的混淆和错误。遵循这一规则不仅有助于编写更清晰、更可靠的代码,还能确保程序的行为符合预期。