# Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
我們直接著手簡化這個話題。只有兩種函數能被你 inherit(繼承):virtual(虛擬的)和 non-virtual(非虛擬的)。然而,重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)永遠都是一個錯誤(參見 Item 36),所以我們可以安全地將我們的討論限制在你繼承了一個 virtual function with a default parameter value(帶有一個缺省參數值的虛擬函數)的情形。
在這種情況下,本 Item 的理由就變得非常地直截了當:virtual functions(虛擬函數)是 dynamically bound(動態綁定),而 default parameter values(缺省參數值)是 statically bound(靜態綁定)。
那又怎樣呢?你說 static(靜態)和 dynamic binding(動態綁定)之間的區別早已塞入你負擔過重的頭腦?(不要忘了,static binding(靜態綁定)也以 early binding(前期綁定)聞名,而 dynamic binding(動態綁定)也以 late binding(后期綁定)聞名。)那么,我們就再來回顧一下。
一個 object(對象)的 static type(靜態類型)就是你在程序文本中聲明給它的 type(類型)。考慮這個 class hierarchy(類繼承體系):
```
// 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;
...
};
```
直觀地看,它看起來就像這個樣子:

現在考慮這些 pointers(指針):
```
Shape *ps; // static type = Shape*
Shape *pc = new Circle; // static type = Shape*
Shape *pr = new Rectangle; // static type = Shape*
```
在本例中,ps,pc 和 pr 全被聲明為 pointer-to-Shape 類型,所以它們全都以此作為它們的 static type(靜態類型)。注意這就使得它們真正指向的東西完全沒有區別——無論如何,它們的 static type(靜態類型)都是 Shape\*。
一個 object(對象)的 dynamic type(動態類型)取決于它當前引用的 object(對象)的 type(類型)。也就是說,它的 dynamic type(動態類型)表明它有怎樣的行為。在上面的例子中,pc 的 dynamic type(動態類型)是 Circle\*,而 pr 的 dynamic type(動態類型)是 Rectangle\*。至于 ps,它沒有一個實際的 dynamic type(動態類型),因為它(還)不能引用任何 object(對象)。
dynamic types(動態類型),就像它的名字所暗示的,能在程序運行中變化,特別是通過 assignments(賦值):
```
ps = pc; // ps's dynamic type is
// now Circle*
ps = pr; // ps's dynamic type is
// now Rectangle*
```
virtual functions(虛擬函數)是 dynamically bound(動態綁定),意味著被調用的特定函數取決于被用來調用它的那個 object(對象)的 dynamic type(動態類型):
```
pc->draw(Shape::Red); // calls Circle::draw(Shape::Red)
pr->draw(Shape::Red); // calls Rectangle::draw(Shape::Red)
```
我知道,這全是老生常談;你的確已經理解了 virtual functions(虛擬函數)。但是,當你考慮 virtual functions with default parameter values(帶有缺省參數值的虛擬函數)時,就全亂了套,因為,如上所述,virtual functions(虛擬函數)是 dynamically bound(動態綁定),但 default parameters(缺省參數)是 statically bound(靜態綁定)。這就意味著你最終調用了一個定義在 derived class(派生類)中的 virtual function(虛擬函數)卻使用了一個來自 base class(基類)的 default parameter value(缺省參數值)。
```
pr->draw(); // calls Rectangle::draw(Shape::Red)!
```
在此情況下,pr 的 dynamic type(動態類型)是 Rectangle\*,所以正像你所希望的,Rectangle 的 virtual function(虛擬函數)被調用。在 Rectangle::draw 中,default parameter value(缺省參數值)是 Green。然而,因為 pr 的 static type(靜態類型)是 Shape\*,這個函數調用的 default parameter value(缺省參數值)是從 Shape class 中取得的,而不是 Rectangle class!導致的結果就是一個調用由“奇怪的和幾乎完全出乎意料的 Shape 和 Rectangle 兩個 classes(類)中的 draw 聲明的混合物”所組成。
ps,pc,和 pr 是 pointers(指針)的事實與這個問題并無因果關系,如果它們是 references(引用),問題依然會存在。唯一重要的事情是 draw 是一個 virtual function(虛擬函數),而它的一個 default parameter values(缺省參數值)在一個 derived class(派生類)中被重定義。
為什么 C++ 要堅持按照這種不正常的方式動作?答案是為了運行時效率。如果 default parameter values(缺省參數值)是 dynamically bound(動態綁定),compilers(編譯器)就必須提供一種方法在運行時確定 virtual functions(虛擬函數)的 parameters(參數)的 default value(s)(缺省值),這比目前在編譯期確定它們的機制更慢而且更復雜。最終的決定偏向了速度和實現的簡單這一邊,而造成的結果就是你現在可以享受高效運行的樂趣,但是,如果你忘記留心本 Item 的建議,就會陷入困惑。
這樣就很徹底而且完美了,但是看看如果你試圖遵循本規則為 base(基類)和 derived classes(派生類)的用戶提供同樣的 default parameter values(缺省參數值)時會發生什么:
```
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) const;
...
};
```
噢,code duplication(代碼重復)。code duplication(代碼重復)帶來 dependencies(依賴關系):如果 Shape 中的 default parameter values(缺省參數值)發生變化,所有重復了它的 derived classes(派生類)必須同時變化。否則它們就陷入重定義一個 inherited default parameter value(通過繼承得到的缺省參數值)。怎么辦呢?
當你要一個 virtual function(虛擬函數)按照你希望的方式運行有困難的時候,考慮可選的替代設計是很明智的,而且 Item 35 給出了多個 virtual function(虛擬函數)的替代方法。替代方法之一是 non-virtual interface idiom (NVI idiom)(非虛擬接口慣用法):用 base class(基類)中的 public non-virtual function(公有非虛擬函數)調用 derived classes(派生類)可能重定義的 private virtual function(私有虛擬函數)。這里,我們用 non-virtual function(非虛擬函數)指定 default parameter(缺省參數),同時使用 virtual function(虛擬函數)做實際的工作:
```
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
}; // done in this func
class Rectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const; // note lack of a
... // default param val.
};
```
因為 non-virtual functions(非虛擬函數)絕不應該被 derived classes(派生類) overridden(覆蓋)(參見 Item 36),這個設計使得 draw 的 color parameter(參數)的 default value(缺省值)應該永遠是 Red 變得明確。
Things to Remember
絕不要重定義一個 inherited default parameter value(通過繼承得到的缺省參數值),因為 default parameter value(缺省參數值)是 statically bound(靜態綁定),而 virtual functions ——應該是你可以 overriding(覆蓋)的僅有的函數——是 dynamically bound(動態綁定)。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射