天天看點

C++之RAII慣用法

C++中的RAII全稱是“Resource acquisition is initialization”,直譯為“資源擷取就是初始化”。但是這翻譯并沒有顯示出這個慣用法的真正内涵。RAII的好處在于它提供了一種資源自動管理的方式,當産生異常、復原等現象時,RAII可以正确地釋放掉資源。

舉個常見的例子:

  1. void Func()  
  2. {  
  3.   FILE *fp;  
  4.   char* filename = "test.txt";  
  5.   if((fp=fopen(filename,"r"))==NULL)  
  6.   {  
  7.       printf("not open");  
  8.       exit(0);  
  9.   }  
  10.   ... // 如果 在使用fp指針時産生異常 并退出  
  11.        // 那麼 fp檔案就沒有正常關閉  
  12.   fclose(fp);  
  13. }  

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

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

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

具體示例代碼如下:

  1. class Resource{};  
  2. class RAII{  
  3. public:  
  4.     RAII(Resource* aResource):r_(aResource){} //擷取資源  
  5.     ~RAII() {delete r_;} //釋放資源  
  6.     Resource* get()    {return r_ ;} //通路資源  
  7. private:  
  8.     Resource* r_;  
  9. };  

比如檔案操作的例子,我們的RAII臨時對象類就可以寫成:

  1. class FileRAII{  
  2.     FileRAII(FILE* aFile):file_(aFile){}  
  3.     ~FileRAII() { fclose(file_); }//在析構函數中進行檔案關閉  
  4.     FILE* get() {return file_;}  
  5.     FILE* file_;  

則上面這個打開檔案的例子就可以用RAII改寫為:

  1.   FileRAII fileRAII(fp);  
  2.        // 那麼 fileRAII在棧展開過程中會被自動釋放,析構函數也就會自動地将fp關閉  
  3.   // 即使所有代碼是都正确執行了,也無需手動釋放fp,fileRAII它的生命期在此結束時,它的析構函數會自動執行!      
  4.  }  

這就是RAII的魅力,它免除了對需要謹慎使用資源時而産生的大量維護代碼。在保證資源正确處理的情況下,還使得代碼的可讀性也提高了不少。

建立自己的RAII類

一般情況下,RAII臨時對象不允許複制和指派,當然更不允許在heap上建立,是以先寫下一個RAII的base類,使子類私有繼承Base類來禁用這些操作:

  1. class RAIIBase  
  2.     RAIIBase(){}  
  3.     ~RAIIBase(){}//由于不能使用該類的指針,定義虛函數是完全沒有必要的  
  4.     RAIIBase (const RAIIBase &);  
  5.     RAIIBase & operator = (const RAIIBase &);  
  6.     void * operator new(size_t size);   
  7.     // 不定義任何成員  

當我們要寫自己的RAII類時就可以直接繼承該類的實作:

  1. template<typename T>  
  2. class ResourceHandle: private RAIIBase //私有繼承 禁用Base的所有繼承操作  
  3.     explicit ResourceHandle(T * aResource):r_(aResource){}//擷取資源  
  4.     ~ResourceHandle() {delete r_;} //釋放資源  
  5.     T *get()    {return r_ ;} //通路資源  
  6.     T * r_;  

将Handle類做成模闆類,這樣就可以将class類型放入其中。另外, ResourceHandle可以根據不同資源類型的釋放形式來定義不同的析構函數。

由于不能使用該類的指針,是以使用虛函數是沒有意義的。

注:自己寫的RAII類并沒有經過大量的實踐,可能存在問題,請三思而慎用。這裡隻是記錄下自己的實作想法。

繼續閱讀