C 箴言 只要有可能就推遲變數定義

2021-05-25 18:23:26 字數 2845 閱讀 5050

在極大程度上,為你的類(包括類模板)和函式(包括函式模板)提供正確的定義是戰鬥的關鍵性部分。一旦你得到正確的結果,相應的實現很大程度上就是直截了當的。但是仍然有一些注意事項需要當心。過早地定義變數會對效能產生拖累。過度使用強制轉換會導致緩慢的,難以維護的,被微妙的 bug 困擾的**。返回乙個類內部構件的控制代碼會破壞封裝並將空懸控制代碼留給客戶。疏忽了對異常產生的影響的考慮會導致資源的洩漏和資料結構的破壞。過分內聯化(inlining)會導致**膨脹。過度的耦合會導致令人無法接受的漫長的建構時間。 這一切問題都可以避免。

只要有可能就推遲變數定義

只要你定義了乙個帶有建構函式和析構函式的型別的變數,當控制流程到達變數定義的時候會使你擔負構造成本,而當變數離開作用域的時候會使你擔負析構成本。如果有無用變數造成這一成本,你就要盡你所能去避免它。

你可能認為你從來不會定義無用的變數,但是也許你應該再想一想。考慮下面這個函式,只要 password 的長度滿足要求,它就返回乙個 password 的加密版本。如果 password 太短,函式就會丟擲乙個定義在標準 c++ 庫中的 logic_error 型別的異常(參見 item 54):

// this function defines the variable "encrypted" too soon

std::string encryptpassword(const std::string& password)

... // do whatever is necessary to place an

// encrypted version of password in encrypted

return encrypted;

}物件 encrypted 在這個函式中並不是完全無用,但是如果丟擲了乙個異常,它就是無用的。換句話說,即使 encryptpassword 丟擲乙個異常,你也要為構造和析構 encrypted 付出代價。因此得出以下結論:你最好將 encrypted 的定義推遲到你確信你真的需要它的時候:

// this function postpones encrypted』s definition until it』s truly necessary

std::string encryptpassword(const std::string& password)

string encrypted;

... // do whatever is necessary to place an

// encrypted version of password in encrypted

return encrypted;

}這一**仍然沒有達到它本可以達到的那樣緊湊,因為定義 encrypted 的時候沒有任何初始化引數。這就意味著很多情況下將使用它的預設建構函式,對於乙個物件你首先應該做的就是給它一些值,這經常可以通過賦值來完成我已經解釋了為什麼預設構造(default-constructing)乙個物件然後賦值給它比用你真正需要它持有的值初始化它更低效。那個分析也適用於此。例如,假設 encryptpassword 的核心部分是在這個函式中完成的:

void encrypt(std::string& s); // encrypts s in place

那麼,encryptpassword 就可以這樣實現,即使它還不是最好的方法:

// this function postpones encrypted』s definition until

// it』s necessary, but it』s still needlessly inefficient

std::string encryptpassword(const std::string& password)

乙個更可取得方法是用 password 初始化 encrypted,從而跳過毫無意義並可能很昂貴的預設構造:

// finally, the best way to define and initialize encrypted

std::string encryptpassword(const std::string& password)

這個建議就是本 item 的標題中的「只要有可能(as long as possible)」的真正含義。你不僅應該推遲乙個變數的定義直到你不得不用它之前的最後一刻,而且應該試圖推遲它的定義直到你得到了它的初始化引數。通過這樣的做法,你可以避免構造和析構無用物件,而且還可以避免不必要的預設構造。更進一步,通過在它們的含義已經非常明確的上下文中初始化它們,有助於對變數的作用文件化。

「但是對於迴圈會如何?」你可能會有這樣的疑問。如果乙個變數僅僅在乙個迴圈內使用,是迴圈外面定義它並在每次迴圈迭代時賦值給它更好一些,還是在迴圈內部定義這個變數更好一些呢?也就是說,下面這兩個大致的結構中哪個更好一些?

widget w;

for (int i = 0; i < n; ++i) }

這裡我將乙個型別 string 的物件換成了乙個型別 widget 的物件,以避免對這個物件的構造、析構或賦值操作的成本的任何已有的預見。

對於 widget 的操作而言,就是下面這兩個方法的成本:

方法 a:1 個建構函式 + 1 個析構函式 + n 個賦值。

方法 b:n 個建構函式 + n 個析構函式。

對於那些賦值的成本低於乙個建構函式/析構函式對的成本的類,方法 a 通常更高效。特別是在 n 變得很大的情況下。否則,方法 b 可能更好一些。此外,方法 a 與方法 b 相比,使得名字 w 在乙個較大的區域(包含迴圈的那個區域)內均可見,這可能會破壞程式的易理解性和可維護性。因此得出以下結論:除非你確信以下兩點:(1)賦值比建構函式/析構函式對成本更低,而且(2)你正在涉及你的**中的效能敏感的部分,否則,你應該預設使用方法 b。

things to remember

·只要有可能就推遲變數定義。這樣可以增加程式的清晰度並提高程式的效能。

盡可能地推遲變數的定義

是的,我們同意c語言中變數要放在模組頭部定義的規定 但在c 中,還是取消這種做法吧,它沒必要,不自然,而且昂貴。還記得嗎?如果定義了乙個有建構函式和析構函式的型別的變數,當程式執行到變數定義之處時,必然面臨構造的開銷 當變數離開它的生命空間時,又要承擔析構的開銷。這意味著定義無用的變數必然伴隨著不必...

條款32 盡可能地推遲變數的定義

此函式太早定義了變數 encrypted string encryptpassword const string password 進行必要的操作,將口令的加密版本 放進encrypted之中 return encrypted 物件encrypted在函式中並非完全沒用,但如果有異常丟擲時,就是無用...

C 程式設計實用技巧 32 盡可能地推遲變數的定義

是的,我們同意c語言中變數要放在模組頭部定義的規定 但在c 中,還是取消這種做法吧,它沒必要,不自然,而且昂貴。還記得嗎?如果定義了乙個有建構函式和析構函式的型別的變數,當程式執行到變數定義之處時,必然面臨構造的開銷 當變數離開它的生命空間時,又要承擔析構的開銷。這意味著定義無用的變數必然伴隨著不必...