讀書筆記 effective c++ Item 26 盡量推遲變量的定義。本站提示廣大學習愛好者:(讀書筆記 effective c++ Item 26 盡量推遲變量的定義)文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effective c++ Item 26 盡量推遲變量的定義正文
每當你定義一種類型的變量時:當控制流到達變量的定義點時,你引入了調用構造函數的開銷,當離開變量的作用域之後,你引入了調用析構函數的開銷。對未使用到的變量同樣會產生開銷,因此對這種定義要盡可能的避免。
2. 普通函數中的變量定義推遲 2.1 變量有可能不會被使用到的例子你可能會想你永遠不會定義未使用的變量,你可能要再考慮考慮。看下面的函數,此函數返回password的加密版本,提供的password需要足夠長。如果password太短,函數會拋出一個logic_error類型的異常,此異常被定義在標准C++庫中(Item 54):
1 // this function defines the variable "encrypted" too soon 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 using namespace std; 8 9 string encrypted; 10 11 if (password.length() < MinimumPasswordLength) { 12 13 throw logic_error("Password is too short"); 14 15 } 16 17 ... // do whatever is necessary to place an 18 19 // encrypted version of password in encrypted 20 21 return encrypted; 22 23 }
對象encrypted不是完全不會被用到,但是如果拋出了異常它就肯定不會被用到。這就是說,如果encryptPassword拋出了異常,你不會用到encrypted,但是你同樣會為encrypted的構造函數和析構函數買單。因此,最好推遲encrypted的定義直到你認為你會使用它:
1 // this function postpones encrypted’s definition until it’s truly necessary 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 using namespace std; 8 9 if (password.length() < MinimumPasswordLength) { 10 11 throw logic_error("Password is too short"); 12 13 } 14 15 string encrypted; 16 17 ... // do whatever is necessary to place an 18 19 // encrypted version of password in encrypted 20 21 return encrypted; 22 23 }
2.2 推遲變量定義的一種方法
上面的代碼看起來還是不夠緊湊,因為encrypted定義時沒有帶任何初始化參數。也就意味著默認構造函數會被調用。在許多情況下,你對一個對象做的第一件事就是給它提供一些值,這通常通過賦值來進行。Item 4解釋了為什麼默認構造一個對象緊接著對其進行賦值要比用一個值對其初始化效率要低。其中的分析在這裡同樣適用。舉個例子,假設encryptPassword函數的最困難的部分在下面的函數中執行:
1 void encrypt(std::string& s); // encrypts s in place
然後encryptPassword可以像下面這樣實現,雖然這可能不是最好的方法:
1 // this function postpones encrypted’s definition until 2 3 // it’s necessary, but it’s still needlessly inefficient 4 5 std::string encryptPassword(const std::string& password) 6 7 { 8 9 ... // import std and check length as above 10 11 string encrypted; // default-construct encrypted 12 13 encrypted = password; // assign to encrypted 14 15 encrypt(encrypted); 16 17 return encrypted; 18 19 }
2.2 推遲變量定義的更好方法
一個更好的方法是用password來初始化encypted,這樣就跳過了無意義的和可能昂貴的默認構造函數:
1 // finally, the best way to define and initialize encrypted 2 3 std::string encryptPassword(const std::string& password) 4 5 { 6 7 ... // import std and check length 8 9 string encrypted(password); // define and initialize via copy 10 11 // constructor 12 13 encrypt(encrypted); 14 15 return encrypted; 16 17 }
2.3 推遲變量定義的真正含義
這個建議是這個條款的標題中的“盡量推遲”的真正含義。你不但要將變量的定義推遲到你必須使用的時候,你同樣應該嘗試將定義推遲到你獲得變量的初始化值的時候。這麼做,你就能避免不必要的構造和析構,也避免了不必要的默認構造函數。並且,通過在意義已經明確的上下文中對變量進行初始化,你也幫助指明了使用此變量的意圖。
3. 如何處理循環中的變量定義這時候你該想了:循環該怎麼處理呢?如果一個變量只在一個循環中被使用,是將將變量定義在循環外,每次循環迭代為其賦值好呢?還是將其定義在循環內部好呢?也即是下面的結構哪個好?
1 // Approach A: define outside loop 2 3 Widget w; 4 5 for (int i = 0; i < n; ++i) { 6 7 w = some value dependent on i; 8 9 ... 10 11 } 12 13 14 15 // Approach B: define inside loop 16 17 for (int i = 0; i < n; ++i) { 18 19 Widget w(some value dependent oni); 20 21 ... 22 23 }
這裡我用一個Widget類型的對象來替換string類型的對象,以避免對執行構造函數,析構函數或者賦值運算符的開銷有任何偏見。
對於Widget來說,兩種方法的開銷如下:
如果賦值運算的開銷比一對構造函數/析構函數要小,方法A更加高效。尤其是在n很大的時候。否則,方法B要更高效。並且方法A比方法B使變量w在更大的范圍內可見,這一點違反了程序的可理解性和可操作性。因此,除非你遇到下面兩點:(1)賦值比構造/析構開銷要小(2)你正在處理對性能敏感的代碼。否則你應該默認使用方法B。