never redefine a function's inherited default parameter value
先將討論簡化,只能繼承兩種函式:virtual 和non-virtual 函式.然而重新定義乙個繼承而來的non-virtual 函式永遠是錯誤的(詳見
條款36),所以可以安全地將本條款的討論侷限於"繼承乙個帶有預設引數值的virtual函式".
這種情況下,本條款成立的理由就非常直接而明確了:virtual 函式系動態繫結,而預設引數值卻是靜態繫結.
物件的所謂靜態型別,就是它在程式中被宣告時所採用的型別,考慮以下的 class 繼承體系:
// 乙個用以描述幾何形狀的class
class shape ;
// 所有形狀都必須提供乙個函式,用來繪出自己
virtual void draw(shapecolor color = red) const = 0;
...};class rectangle : public shape ;
public circle : public shape ;
考慮這些指標:
shape *ps;
shape *pc = new circle;
shape *pr = new rectangle;
本例中ps,pc,pr都被宣告為pointer-to-shape型別,所以它們都以它為靜態型別,不論它們真正指向什麼,它們的靜態型別都是shape*.
物件的所謂動態型別則是指"目前所指物件的型別".
也就是說,動態型別可以表現出乙個物件將會有什麼行為.以上例而言,pc的動態型別是circle*,pr的動態型別是rectangle*.動態型別如其名稱所示,可在程式執行過程中改變(通常是經由賦值動作):
ps = pc;
ps = pr;
virtual 函式系動態繫結而來,意思是呼叫乙個 virtual 函式時,究竟呼叫哪乙份函式實現**,取決於發出呼叫的那個物件的動態型別:
pc->draw(shape::red); // 呼叫circle::draw(shape::red)
pr->draw(shape::red); // 呼叫rectangle::draw(shape::red)
當考慮帶有預設引數值的 virtual 函式.virtual 函式是動態繫結,而預設引數確實靜態繫結.因此可能會在"呼叫乙個定義於derived class內的virtual函式"的同時,卻使用base class 為它所指定的預設引數值:
pr->draw(); // 呼叫rectangle::draw(shape::red)
此例中,pr的動態型別是rectangle*,所以呼叫的是rectangle的 virtual 函式.rectangle::draw函式的預設引數應該是green,但由於pr的靜態型別是shape*,所以此呼叫的預設引數值來自shape class 而非rectangle class .結局是這個函式呼叫者奇怪並且幾乎絕對沒有人預料得到的組合,由shape class 和rectangle class 的draw宣告式各出一半力.
重點在於draw是個 virtual 函式,而它有個預設引數值在derived class 中被重定義了.
為什麼c++堅持以這種乖張的方式運作呢?答案在於執行期效率.如果預設引數值是動態繫結,編譯器就必須有某種辦法在執行期為 virtual 函式決定適當引數預設值.這比目前實行的"在編譯器決定"的機制更慢且更複雜.為了程式的執行速度和編譯器實現上的簡易度,c++做了這樣的取捨,其結果就是如今的執行效率.
如果遵守這條規則,並且同時提供預設引數值給base和derived class 的使用者,又會發生什麼事?
class shape ;
virtual void draw(shapecolor color = red) const = 0;
...};class rectangle : public shape ;
**重複,更糟糕的是,**重複又帶著相依性:如果shape內的預設引數值改變了,所有"重複給定預設數值"的那些derived class 也必須改變,否則它們最終會導致"重複定義乙個繼承而來的預設引數值".怎麼辦?
當想要 virtual 函式表現出想要的行為但遭遇麻煩,聰明的做法是考慮替代設計.
條款35列出了不少 virtual 函式的替代設計,其中之一是nvi(non-virtual inte***ce)手法:令base class 內的乙個 public non-virtual 函式呼叫 private virtual 函式,後者可被derived class 重新定義.這裡可以讓non-virtual 函式指定預設引數,而 private virtual 函式負責真正的工作:
class shape ;
void draw(shapecolor color = red) const
...private:
virtual void dodraw(shapecolor color) const = 0; // 真正的工作在此處完成
};class rectangle : public shape ;
由於non-virtual 函式應該絕對不被derived class 覆寫(詳見條款36),這個設計很清楚地使得draw函式的color預設引數總是red.
注意:
絕對不要重新定義乙個繼承而來的預設引數值,因為預設引數值都是靜態繫結,而 virtual 函式——唯一應該覆寫的東西——卻是動態繫結.
Effective C 條款8 第2章
prevent exception from leving destructors.c 並不禁止析構函式吐出異常,但它不鼓勵這樣做.這是有原因的,考慮以下 class widget 假設這個可能吐出乙個異常 void dosomething v在這裡被自動銷毀當vector v被銷毀,它有責任銷毀其...
Effective C 條款15 第3章
provide access to raw resources in resources managing classes 資源管理類 resource managing classes 很棒.它們是對抗資源洩露的堡壘.在乙個良好的環境中將依賴這樣的classes來處理和資源之間的所有互動.而不是直...
Effective C 條款23 第4章
prefer non member non friend functions to member functions 想象有個 class 用來表示網頁瀏覽器,這樣的 class 可能提供眾多函式,如下所示 class webbrowser 許多使用者會想一整個執行所有這些動作,因此webbrows...