跳轉到內容

更多 C++ 慣用法/資源獲取即初始化

來自華夏公益教科書,開放書籍,開放世界

資源獲取即初始化

[編輯 | 編輯原始碼]
  • 保證在作用域結束時釋放資源。
  • 提供基本異常安全保證。

也稱為

[編輯 | 編輯原始碼]
  • 執行周圍物件
  • 資源釋放即最終化
  • 作用域繫結資源管理

在函式作用域中獲取的資源應在離開作用域之前釋放,除非所有權被轉移到另一個作用域或物件。這通常意味著一對函式呼叫 - 一個用於獲取資源,另一個用於釋放資源。例如,new/delete、malloc/free、acquire/release、file-open/file-close、nested_count++/nested_count-- 等。很容易忘記編寫資源管理“契約”的“釋放”部分。有時資源釋放函式永遠不會被呼叫:當控制流由於返回或異常而離開作用域時,就會發生這種情況。過分相信程式設計師會在現在和將來在所有可能的情況下呼叫資源釋放操作是極其危險的。下面給出了一些示例。

void foo ()
{
  char * ch = new char [100];
  if (...)
     if (...)
        return;
     else if (...)
            if (...)
  else
     throw "ERROR";

  delete [] ch; // This may not be invoked... memory leak!
}
void bar ()
{
  lock.acquire();
  if (...)
     if (...)
        return;
  else
     throw "ERROR";

  lock.release(); // This may not be invoked... deadlock!
}

這是一個通用的控制流抽象問題。資源獲取即初始化 (RAII) 是 C++ 中一個非常流行的慣用法,它以巧妙的方式減輕了呼叫“資源釋放”操作的負擔。

解決方案和示例程式碼

[編輯 | 編輯原始碼]

其思想是在作用域中的物件的解構函式中包裝資源釋放操作。語言保證解構函式(針對成功構造的物件)將在控制流由於 return 語句或異常而離開作用域時始終被呼叫。

//  Private copy constructor and copy assignment ensure classes derived 
//  from class NonCopyable cannot be copied.
class NonCopyable 
{
   NonCopyable (NonCopyable const &); // private copy constructor
   NonCopyable & operator = (NonCopyable const &); // private assignment operator
};
template <class T>
class AutoDelete : NonCopyable
{
  public:
    AutoDelete (T * p = 0) : ptr_(p) {}
    ~AutoDelete () throw() { delete ptr_; } 
  private:
    T *ptr_;
};

class ScopedLock : NonCopyable// Scoped Lock idiom
{
  public:
    ScopedLock (Lock & l) : lock_(l) { lock_.acquire(); }
    ~ScopedLock () throw () { lock_.release(); } 
  private:
    Lock& lock_;
};

void foo ()
{
  X * p = new X;
  // C++ guarantees that the destructors of objects on the stack will be called, even if an exception is thrown.
  AutoDelete<X> safe_del(p); // destruction of safe_del will be called and lock will be released when it goes out of function foo scope

  if (...)
    if (...)
      return; 
 
  // No need to call delete here.
  // Destructor of safe_del will delete memory
}
void X::bar()
{
  ScopedLock safe_lock(l); // Lock will be released certainly
  if (...)
    if (...)
      throw "ERROR"; 
  // No need to call release here.
  // Destructor of safe_lock will release the lock
}

在 RAII 慣用法中,在建構函式中獲取資源不是強制性的,但在解構函式中釋放資源是關鍵。因此,它也被稱為(儘管很少)資源釋放即最終化慣用法。在此慣用法中,解構函式不應丟擲異常這一點很重要。因此,解構函式具有 no-throw 規範,但這是可選的。std::auto_ptr 和 boost::scoped_ptr 是快速將 RAII 慣用法用於記憶體資源的方法。RAII 還用於確保異常安全。RAII 使得在不廣泛使用 try/catch 塊的情況下避免資源洩漏成為可能,並且在軟體行業中被廣泛使用。

許多使用 RAII 管理資源的類沒有合法的複製語義(例如,網路連線、資料庫遊標、互斥鎖)。前面顯示的NonCopyable 類阻止實現 RAII 的物件的複製。它只是透過將複製建構函式和複製賦值運算子設為私有,來阻止對它們的訪問。boost::scoped_ptr 是一個這樣的類的示例,它在持有記憶體資源時阻止複製。NonCopyable 類明確說明了此意圖,並在錯誤使用時阻止編譯。此類類不應在 STL 容器中使用。但是,並非每個實現 RAII 的資源管理類都必須像上面這兩個示例一樣不可複製。如果需要複製語義,則可以使用boost::shared_ptr 來管理記憶體資源。一般來說,非侵入式引用計數 用於提供複製語義以及 RAII。

RAII 並非沒有侷限性。不是記憶體的資源,必須確定性地釋放並且可能丟擲異常,通常不能很好地由 C++ 解構函式處理。這是因為 C++ 解構函式不能將錯誤傳播到封閉的作用域(這可能會最終導致)。它沒有返回值,也不應傳播異常。如果可能出現異常,則解構函式必須以某種方式在自身內部處理異常情況。儘管如此,RAII 仍然是 C++ 中使用最廣泛的資源管理慣用法。

已知用途

[編輯 | 編輯原始碼]
  • 幾乎所有非平凡的 C++ 軟體
  • std::unique_ptr(和 std::auto_ptr)
  • std::shared_ptr
  • boost::mutex::scoped_lock
[編輯 | 編輯原始碼]

參考資料

[編輯 | 編輯原始碼]
華夏公益教科書