C++ 資源管理之 RAII


RAII,它是“Resource Acquisition Is Initialization”的首字母縮寫。也稱為“資源獲取就是初始化”,是c++等編程語言常用的管理資源、避免內存泄露的方法。它保證在任何情況下,使用對象時先構造對象,最后析構對象。

RAII的好處在於它提供了一種資源自動管理的方式,當產生異常、回滾等現象時,RAII可以正確地釋放掉資源。

當講述C++資源管理時,Bjarne這樣寫道:

使用局部對象管理資源的技術通常稱為“資源獲取就是初始化”。這種通用技術依賴於構造函數和析構函數的性質以及它們與異常處理的交互作用。

 

RAII 技術:

我們在C++中經常使用new申請了內存空間,但是卻也經常忘記delete回收申請的空間,容易造成內存溢出,於是RAII技術就誕生了,來解決這樣的問題。

RAII(Resource Acquisition Is Initialization)機制是Bjarne Stroustrup首先提出的,是一種利用對象生命周期來控制程序資源(如內存、文件句柄、網絡連接、互斥量等等)的簡單技術。

我們知道在函數內部的一些成員是放置在棧空間上的,當函數返回時,這些棧上的局部變量就會立即釋放空間,於是Bjarne Stroustrup就想到確保能運行資源釋放代碼的地方就是在這個程序段(棧)中放置的對象的析構函數了,因為stack winding會保證它們的析構函數都會被執行。RAII就利用了棧里面的變量的這一特點。

Stack Winding & Unwinding

當程序運行時,每一個函數(包括數據、寄存器、程序計數器,等等)在調用時,都被映射到棧上。這就是 stack winding。

Unwinding 是以相反順序把函數從棧上移除的過程。

正常的 stack unwinding 發生在函數返回時;不正常的情況,比如引發異常,調用setjmplongjmp,也會導致 stack unwinding。

可見 stack unwinding 的過程中,局部對象的析構函數將逐一被調用。這也就是 RAII 工作的原理,它是由語言和編譯器來保證的。

RAII 的一般做法是這樣的:在對象構造時獲取資源,接着控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在對象析構的時候釋放資源。借此,我們實際上把管理一份資源的責任托管給了一個存放在棧空間上的局部對象。
這種做法有兩大好處: 
(1)不需要顯式地釋放資源。 
(2)采用這種方式,對象所需的資源在其生命期內始終保持有效。

 

首先讓我們來明確資源的概念,在計算機系統中,資源是數量有限且對系統正常運轉具有一定作用的元素。

比如,內存,文件句柄,網絡套接字(network sockets),互斥鎖(mutex locks)等等,它們都屬於系統資源。

由於資源的數量不是無限的,有的資源甚至在整個系統中僅有一份,因此我們在使用資源時必須嚴格遵循的步驟是:

  • 獲取資源
  • 使用資源
  • 釋放資源

 例子:

void Func()  
{  
  FILE *fp;  
  char* filename = "test.txt";  
  if((fp=fopen(filename,"r"))==NULL)  
  {  
      printf("not open");  
      exit(0);  
  }  
  ... // 如果 在使用fp指針時產生異常 並退出  
       // 那么 fp文件就沒有正常關閉  
      
  fclose(fp);  
}  

 

在資源的獲取到釋放之間,我們往往需要使用資源,但常常一些不可預計的異常是在使用過程中產生,就會使資源的釋放環節沒有得到執行。

 

此時,就可以讓RAII慣用法大顯身手了。

 

RAII的實現原理很簡單,利用stack上的臨時對象生命期是程序自動管理的這一特點,將我們的資源釋放操作封裝在一個臨時對象中。

具體示例代碼如下:

 

class Resource{};  
class RAII{  
public:  
    RAII(Resource* aResource):r_(aResource){} //獲取資源  
    ~RAII() {delete r_;} //釋放資源  
    Resource* get()    {return r_ ;} //訪問資源  
private:  
    Resource* r_;  
};  

 

 

void UseResources()
{
    // 獲取資源1
    // ...
    // 獲取資源n
   
    // 使用這些資源
   
    // 釋放資源n
    // ...
    // 釋放資源1
}

 

不難看出資源管理技術的關鍵在於:要保證資源的釋放順序與獲取順序嚴格相反。

這自然使我們聯想到局部對象的創建和銷毀過程。在C++中,定義在棧空間上的局部對象稱為自動存儲(automatic memory)對象。

管理局部對象的任務非常簡單,因為它們的創建和銷毀工作是由系統自動完成的。

我們只需在某個作用域(scope)中定義局部對象(這時系統自動調用構造函數以創建對象),然后就可以放心大膽地使用之,而不必擔心有關善后工作;當控制流程超出這個作用域的范圍時,系統會自動調用析構函數,從而銷毀該對象。

 

讀者可能會說:如果系統中的資源也具有如同局部對象一樣的特性,自動獲取,自動釋放,那該有多么美妙啊!。事實上,您的想法已經與RAII不謀而合了。既然類是C++中的主要抽象工具,那么就將資源抽象為類,用局部對象來表示資源,把管理資源的任務轉化為管理局部對象的任務。這就是RAII慣用法的真諦!可以毫不誇張地說,RAII有效地實現了C++資源管理的自動化。

 

綜上所述,RAII的本質內容是用對象代表資源,把管理資源的任務轉化為管理對象的任務,將資源的獲取和釋放與對象的構造和析構對應起來,從而確保在對象的生存期內資源始終有效,對象銷毀時資源必被釋放。

換句話說,擁有對象就等於擁有資源,對象存在則資源必定存在。由此可見,RAII慣用法是進行資源管理的有力武器。C++程序員依靠RAII寫出的代碼不僅簡潔優雅,而且做到了異常安全。

 

參考:http://blog.csdn.net/doc_sgl/article/details/43028009

     http://www.cnblogs.com/hsinwang/articles/214663.html

     http://blog.csdn.net/hunter8777/article/details/6327704


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM