C++之RAII慣用法

標籤: raiic++fpfileinitializationclassc++

C++中的RAII全稱是「Resource acquisition is initialization」,直譯爲「資源獲取就是初始化」。可是這翻譯並無顯示出這個慣用法的真正內涵。RAII的好處在於它提供了一種資源自動管理的方式,當產生異常、回滾等現象時,RAII能夠正確地釋放掉資源。函數

舉個常見的例子:ui

[cpp] view plain copy翻譯

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

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

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

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

具體示例代碼以下:繼承

[cpp] view plain copyci

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

好比文件操做的例子,咱們的RAII臨時對象類就能夠寫成:資源

[cpp] view plain copy

class FileRAII{  
public:  
    FileRAII(FILE* aFile):file_(aFile){}  
    ~FileRAII() { fclose(file_); }//在析構函數中進行文件關閉  
    FILE* get() {return file_;}  
private:  
    FILE* file_;  
};

則上面這個打開文件的例子就能夠用RAII改寫爲:

[cpp] view plain copy

void Func()  
{  
  FILE *fp;  
  char* filename = "test.txt";  
  if((fp=fopen(filename,"r"))==NULL)  
  {  
      printf("not open");  
      exit(0);  
  }  
  FileRAII fileRAII(fp);  
  ... // 若是 在使用fp指針時產生異常 並退出  
       // 那麼 fileRAII在棧展開過程當中會被自動釋放,析構函數也就會自動地將fp關閉  
    
  // 即便全部代碼是都正確執行了,也無需手動釋放fp,fileRAII它的生命期在此結束時,它的析構函數會自動執行!      
 }

這就是RAII的魅力,它免除了對須要謹慎使用資源時而產生的大量維護代碼。在保證資源正確處理的狀況下,還使得代碼的可讀性也提升了很多。

建立本身的RAII類

通常狀況下,RAII臨時對象不容許複製和賦值,固然更不容許在heap上建立,因此先寫下一個RAII的base類,使子類私有繼承Base類來禁用這些操做:

[cpp] view plain copy

class RAIIBase  
{  
public:  
    RAIIBase(){}  
    ~RAIIBase(){}//因爲不能使用該類的指針,定義虛函數是徹底沒有必要的  
      
    RAIIBase (const RAIIBase &);  
    RAIIBase & operator = (const RAIIBase &);  
    void * operator new(size_t size);   
    // 不定義任何成員  
};

當咱們要寫本身的RAII類時就能夠直接繼承該類的實現:

[cpp] view plain copy

template<typename T>  
class ResourceHandle: private RAIIBase //私有繼承 禁用Base的全部繼承操做  
{  
public:  
    explicit ResourceHandle(T * aResource):r_(aResource){}//獲取資源  
    ~ResourceHandle() {delete r_;} //釋放資源  
    T *get()    {return r_ ;} //訪問資源  
private:  
    T * r_;  
};

將Handle類作成模板類,這樣就能夠將class類型放入其中。另外, ResourceHandle能夠根據不一樣資源類型的釋放形式來定義不一樣的析構函數。

因爲不能使用該類的指針,因此使用虛函數是沒有意義的。

注:本身寫的RAII類並無通過大量的實踐,可能存在問題,請三思而慎用。這裏只是記錄下本身的實現想法。

相關文章
相關標籤/搜索