程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++箴言:只要有可能就推遲變量定義

C++箴言:只要有可能就推遲變量定義

編輯:C++入門知識

  在極大程度上,為你的類(包括類模板)和函數(包括函數模板)提供正確的定義是戰斗的要害性部分。一旦你得到正確的結果,相應的實現很大程度上就是直截了當的。但是仍然有一些注重事項需要當心。過早地定義變量會對性能產生拖累。過度使用強制轉換會導致緩慢的,難以維護的,被微妙的 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)
  {
    using namespace std;
  
    string encrypted;
  
    if (password.length() < MinimumPasswordLength) {
   throw logic_error("Password is too short");
    }
    ... // 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)
  {
    using namespace std;
  
    if (password.length() < MinimumPasswordLength) {
   throw logic_error("Password is too short");
    }
  
    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)
  {
    ... // check length as above
  
    std::string encrypted; // default-construct encrypted
    encrypted = password; // assign to encrypted
  
    encrypt(encrypted);
    return encrypted;
  }

  一個更可取得方法是用 password 初始化 encrypted,從而跳過毫無意義並可能很昂貴的缺省構造:
  
  // finally, the best way to define and initialize encrypted
  std::string encryptPassword(const std::string& password)
  {
    ... // check length
  
    std::string encrypted(password); // define and initialize
    // via copy constructor
  
    encrypt(encrypted);
    return encrypted;
  }

  這個建議就是本 Item 的標題中的“只要有可能(as long as possible)”的真正含義。你不僅應該推遲一個變量的定義直到你不得不用它之前的最後一刻,而且應該試圖推遲它的定義直到你得到了它的初始化參數。通過這樣的做法,你可以避免構造和析構無用對象,而且還可以避免不必要的缺省構造。更進一步,通過在它們的含義已經非常明確的上下文中初始化它們,有助於對變量的作用文檔化。
  
  “但是對於循環會如何?”你可能會有這樣的疑問。假如一個變量僅僅在一個循環內使用,是循環外面定義它並在每次循環迭代時賦值給它更好一些,還是在循環內部定義這個變量更好一些呢?也就是說,下面這兩個大致的結構中哪個更好一些?
  
  // Approach A: define outside loop // Approach B: define inside loop
  
  Widget w;
  for (int i = 0; i < n; ++i){ for (int i = 0; i < n; ++i) {
  w = some value dependent on i; Widget w(some value dependent on i);
  ... ...
  } }

  這裡我將一個類型 string 的對象換成了一個類型 Widget 的對象,以避免對這個對象的構造、析構或賦值操作的成本的任何已有的預見。
  
  對於 Widget 的操作而言,就是下面這兩個方法的成本:
  
  方法 A:1 個構造函數 + 1 個析構函數 + n 個賦值。
  
  方法 B:n 個構造函數 + n 個析構函數。
  
  對於那些賦值的成本低於一個構造函數/析構函數對的成本的類,方法 A 通常更高效。非凡是在 n 變得很大的情況下。否則,方法 B 可能更好一些。此外,方法 A 與方法 B 相比,使得名字 w 在一個較大的區域(包含循環的那個區域)內均可見,這可能會破壞程序的易理解性和可維護性。因此得出以下結論:除非你確信以下兩點:(1)賦值比構造函數/析構函數對成本更低,而且(2)你正在涉及你的代碼中的性能敏感的部分,否則,你應該默認使用方法 B。
  
  Things to Remember
  
  ·只要有可能就推遲變量定義。這樣可以增加程序的清楚度並提高程序的性能。
  
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved