C++之 RAII基本理解与使用

来源:互联网 发布:淘宝店拍照技巧 编辑:程序博客网 时间:2024/06/03 17:12
产生原因
     在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。
将初始化和资源释放都移动到一个包装类中的好处:
     - 保证了资源的正常释放
     - 省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
     - 简化代码体积。

简介:
RAII,也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象

   理解: RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。

根据RAII  对资源的所有权可分为常性类型变性类型,代表者分别是boost:shared_ptr<> 和std::auto_ptr<>;
                 从所管资源的初始化位置上可分为外部初始化类型和内部初始化类型。

常性类型是指获取资源的地点是构造函数,释放点是析构函数,并且在这两点之间的一段时间里,任何对该RAII类型实例的操纵都不应该从它手里夺走资源的所有权。

变性类型是指可以中途被设置为接管另一个资源,或者干脆被置为不拥有任何资源。

外部初始化类型是指资源在外部被创建,并被传给RAII实例的构造函数,后者进而接管了其所有权。boost:shared_ptr<>和std::auto_ptr<>都是此类型。与之相对的是内部初始化类型。
     常性且内部初始化的类型是最为纯粹的RAII形式,最容易理解,最容易编码。


RAII使用
     每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,实现自动资源释放。

使用举例
<span style="font-size:14px;">void Func()  {    FILE *fp;    char* filename = "test.txt";    if((fp=fopen(filename,"r"))==NULL)    {        printf("not open");        exit(0);    }    ... // 如果 在使用fp指针时产生异常 并退出         // 那么 fp文件就没有正常关闭          fclose(fp);  }  </span>

在资源的获取到释放之间,我们往往需要使用资源,但常常一些不可预计的异常是在使用过程中产生,就会使资源的释放环节没有得到执行。此时,就可以让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临时对象不允许复制和赋值,当然更不允许在heap上创建,所以先写下一个RAII的base类,使子类私有继承Base类来禁用这些操作:
<span style="font-size:14px;">class RAIIBase  {  public:      RAIIBase(){}      ~RAIIBase(){}//由于不能使用该类的指针,定义虚函数是完全没有必要的            RAIIBase (const RAIIBase &);      RAIIBase & operator = (const RAIIBase &);      void * operator new(size_t size);       // 不定义任何成员  };  </span>
当我们要写自己的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可以根据不同资源类型的释放形式来定义不同的析构函数。  由于不能使用该类的指针,所以使用虚函数是没有意义的。


总结1:
     RAII的核心思想 是使用对象管理资源 , 对象“消亡”则自动释放资源理解和使用RAII能使软件设计更清晰,代码更健壮。
     与大名鼎鼎的垃圾收集(GC)不同的是,RAII可管理广义的资源,而垃圾收集只关注“内存泄漏”,不关心诸如文件句柄、同步对象等一些系统资源的泄漏问题。RAII能使程序员确定资源释放的时机,这也正是C++/CLI引入确定性资源清理的原因。

总结2:
     RAII的本质内容是用对象代表资源,把管理资源的任务转化为管理对象的任务将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源必被释放。换句话说,拥有对象就等于拥有资源,对象存在则资源必定存在。

参考:
http://developer.51cto.com/art/201106/267946.htm
http://www.cnblogs.com/gnuhpc/archive/2012/12/04/2802307.html
http://www.cnblogs.com/hsinwang/articles/214663.html
http://blog.csdn.net/hunter8777/article/details/6327704


0 0