Effective C 條款37 第6章

2021-07-05 07:29:36 字數 2992 閱讀 8285

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...