C++之RAII慣用法

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

舉個常見的例子:

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_;
};
比如文件操作的例子,我們的RAII臨時對象類就可以寫成:

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

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

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類來禁用這些操作:

class RAIIBase
{
public:
    RAIIBase(){}
    ~RAIIBase(){}//由於不能使用該類的指針,定義虛函數是完全沒有必要的
    
    RAIIBase (const RAIIBase &);
    RAIIBase & operator = (const RAIIBase &);
    void * operator new(size_t size); 
    // 不定義任何成員
};
當我們要寫自己的RAII類時就可以直接繼承該類的實現:

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類並沒有經過大量的實踐,可能存在問題,請三思而慎用。這裏只是記錄下自己的實現想法。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章