The RAII(resource acquisition is initialization) of C++

RAII is a must know technique for c++ programmers, no excuse.

     RAII( resource acquisition is initialization), in a nutshell, is same as " let the object handle the resource".I use the word  resource but  not memory, because  RAII can guard  more than memory(mutex, db connnection, socket, file and every resource you can think of).There are many  blogs,  famous technical forum,  gurus suggestion, mention and emphasize how important and what  RAII is.

    C++ is a big, complexity language, I have no idea I need to take how many years to learn every corners of this powerful beast, but we  do not need to learn all of it. We only need to know the most essential part of c++,  RAII is one of them.You may not use any  TMP( template meta programming) ,  multi inheritance(please think carefully before you use this feature), design any  allocators, don't know what is  policy based design, have no idea how  type_traitswork, do not familiar with  operator overloading, but you will always find out that  RAII could help you write good codes, make your codes become much more easier to  maintainreadand more  robust.

   If you don't believe it, let us look at a simple example. 


#include <iostream>
#include <new>

int main()
{
    int *arr = nullptr;
    int *max = nullptr;
    double *double_arr = nullptr;

    try{
    arr = new int[100];
    max = new int(300);
    double_arr = new double[300];

    //.....do something......

    //handle your garbages if something happen
    goto cleanUp;
    //......
  }catch(std::bad_alloc const &ex){
    std::cerr<<ex.what()<<std::endl;
  }

  cleanUp:
    if(arr){
      delete []arr;
      arr = nullptr;
    }
    if(max){
      delete max;
      max = nullptr;
    }
    if(double_arr){
      delete []double_arr;
      double_arr = nullptr;
    }
}

    What is the problem with this example?They are perfectly legal and safe in C++?If you don't get it, let me ask you a problem.

Q : What if we need to handle more resource?

Naive answer(bad practice) : add more if else

The suck codes proposed by the naive answer may look like


#include <iostream>
#include <new>

int main()
{
    int *arr = nullptr;
    int *max = nullptr;
    double *double_arr = nullptr;
    float *float_max = nullptr;
    float *float_arr = nullptr;

    try{
        arr = new int[100];
        max = new int(300);
        double_arr = new double[300];
        float_max = new float(3.14);
        float_arr = new float[300];

        //.....do something......

        //handle your garbages if something happen
        goto cleanUp;
        //......
    }catch(std::bad_alloc const &ex){
        std::cerr<<ex.what()<<std::endl;
    }

cleanUp:
    if(arr){
        delete []arr;
        arr = nullptr;
    }
    if(max){
        delete max;
        max = nullptr;
    }
    if(double_arr){
        delete []double_arr;
        double_arr = nullptr;
    }
    if(float_max){
        delete float_max;
        float_max = nullptr;
    }
    if(float_arr){
        delete []float_arr;
        float_arr = nullptr;
    }
}

   You will have to alter  two places whenever you need to add or delete the resources.This kind of c style codes are  error pronehard to maintain and  unbearable, they are almost  as worse as(even worse than it if we have to take care of exception) the old school c codes which heavily depends on goto to clean up resources, this kind of codes will become unreadable in short times. I believe this is one of the reason why  Bjarne said " Aim to write idiomatic C++: avoid simply writing code in the style of your previous language using C++ syntax; there is little to be gained from simply changing syntax.".For any C++ programmers who know how important and powerful  RAII is, they can't bear this kind of horrible codes.

    How could  RAII help us?


#include <iostream>
#include <memory>
#include <new>
#include <vector>

int main()
{
  try{
   std::vector<int> arr(100);
   std::unique_ptr<int> max(new int(300));
   std::vector<double> double_arr(300);
   std::unique_ptr<double> double_max(new double(300));
   std::vector<float> float_arr(100);
   std::unique_ptr<float> float_max(new float(100));

   //...do something....
   
   //the object will release the resources automatically
   //even the exceptions thrown

  }catch(std::bad_alloc const &ex){
    std::cerr<<ex.what()<<std::endl;
  }
}
    

    Do you see that?The codes become  much more easier to read, because we don't need to release the resource manually, we don't need to change the codes in two places.The codes become more  robust, because we  will not forget to release the resources again.More than that, it is  exception safe too.c++ is good because you  can deal with pointer and memory management, in c you  have to deal with pointer and memory management.

    There are many kinds of coding standards, rules in many teams, not all coding standards suit for all teams and all applications.Whatever it is,  RAII is a  must know technique and should be obey when you can. If you are using  delete in your codes, maybe you are doing something  wrong or writing  worse codes in C++, even you need to use delete(building some infrastructures, like smart pointer, containers and so on), do remember to  protect it by object, always remember to follow the rule of  RAII.Pointer is  good at  access data, but  not resource management, leave your resource to class( RAII).

    In my humble opinion, if a C++ programmer don't know the concepts of  RAII, he/she will generate  lower quality, worse performance, less reliable codes most of the times(just like the suck codes show by the examples).  It is always a good question to test your interviewers, ask them they notice how important  RAII in C++ or not.If they don't know it, don't hire them(or teach them if you think the candidates are worth to).If you were an interviewer and find out the company don't take  RAII seriously, then I bet the codes of this company must be extremely messy, buggy, hard to maintain and stinking. The more you realize about how important  RAII is, the more you can't stand with those  alien artifacts






C++中的RAII全称是“Resource acquisition is initialization”,直译为“资源获取就是初始化”。但是这翻译并没有显示出这个惯用法的真正内涵。RAII的好处在于它提供了一种资源自动管理的方式,当产生异常、回滚等现象时,RAII可以正确地释放掉资源。

举个常见的例子:

[cpp]  view plain copy
  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.       
  13.   fclose(fp);  
  14. }  


在资源的获取到释放之间,我们往往需要使用资源,但常常一些不可预计的异常是在使用过程中产生,就会使资源的释放环节没有得到执行。

此时,就可以让RAII惯用法大显身手了。

 

RAII的实现原理很简单,利用stack上的临时对象生命期是程序自动管理的这一特点,将我们的资源释放操作封装在一个临时对象中。

具体示例代码如下:

[cpp]  view plain copy
  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临时对象类就可以写成:

[cpp]  view plain copy
  1. class FileRAII{  
  2. public:  
  3.     FileRAII(FILE* aFile):file_(aFile){}  
  4.     ~FileRAII() { fclose(file_); }//在析构函数中进行文件关闭  
  5.     FILE* get() {return file_;}  
  6. private:  
  7.     FILE* file_;  
  8. };  

则上面这个打开文件的例子就可以用RAII改写为:

[cpp]  view plain copy
  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.   FileRAII fileRAII(fp);  
  11.   ... // 如果 在使用fp指针时产生异常 并退出  
  12.        // 那么 fileRAII在栈展开过程中会被自动释放,析构函数也就会自动地将fp关闭  
  13.     
  14.   // 即使所有代码是都正确执行了,也无需手动释放fp,fileRAII它的生命期在此结束时,它的析构函数会自动执行!      
  15.  }  

这就是RAII的魅力,它免除了对需要谨慎使用资源时而产生的大量维护代码。在保证资源正确处理的情况下,还使得代码的可读性也提高了不少。

 

创建自己的RAII类

一般情况下,RAII临时对象不允许复制和赋值,当然更不允许在heap上创建,所以先写下一个RAII的base类,使子类私有继承Base类来禁用这些操作:

[cpp]  view plain copy
  1. class RAIIBase  
  2. {  
  3. public:  
  4.     RAIIBase(){}  
  5.     ~RAIIBase(){}//由于不能使用该类的指针,定义虚函数是完全没有必要的  
  6.       
  7.     RAIIBase (const RAIIBase &);  
  8.     RAIIBase & operator = (const RAIIBase &);  
  9.     void * operator new(size_t size);   
  10.     // 不定义任何成员  
  11. };  

当我们要写自己的RAII类时就可以直接继承该类的实现:

[cpp]  view plain copy
  1. template<typename T>  
  2. class ResourceHandle: private RAIIBase //私有继承 禁用Base的所有继承操作  
  3. {  
  4. public:  
  5.     explicit ResourceHandle(T * aResource):r_(aResource){}//获取资源  
  6.     ~ResourceHandle() {delete r_;} //释放资源  
  7.     T *get()    {return r_ ;} //访问资源  
  8. private:  
  9.     T * r_;  
  10. };  

 

将Handle类做成模板类,这样就可以将class类型放入其中。另外, ResourceHandle可以根据不同资源类型的释放形式来定义不同的析构函数。

由于不能使用该类的指针,所以使用虚函数是没有意义的。

注:自己写的RAII类并没有经过大量的实践,可能存在问题,请三思而慎用。这里只是记录下自己的实现想法


你可能感兴趣的:(The RAII(resource acquisition is initialization) of C++)