RAII(资源获取即初始化)详解

概念

使用局部对象管理资源的技术通常称为“资源获取就是初始化”

Resource Acquisition Is Initialization 机制是Bjarne Stroustrup首先提出的。要解决的是这样一个问题:

在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。将初始化和资源释放都移动到一个包装类中的好处:

资源管理技术的关键在于:要保证资源的释放顺序与获取顺序严格相反。这自然使我们联想到局部对象的创建和销毁过程。

管理局部对象的任务非常简单,因为它们的创建和销毁工作是由系统自动完成的。我们只需在某个作用域(scope)中定义局部对象(这时系统自动调用构造函数以创建对象),然后就可以放心大胆地使用之,而不必担心有关善后工作;当控制流程超出这个作用域的范围时,系统会自动调用析构函数,从而销毁该对象。

将资源抽象为类,用局部对象来表示资源,把管理资源的任务转化为管理局部对象的任务。这就是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{}; :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惯用法同样适用于需要管理多个资源的复杂对象。例如,Widget类的构造函数要获取两个资源:文件myFile和互斥锁myLock。每个资源的获取都有可能失败并且抛出异常。为了正常使用Widget对象,这里我们必须维护一个不变式(invariant):当调用构造函数时,要么两个资源全都获得,对象创建成功;要么两个资源都没得到,对象创建失败。获取了文件而没有得到互斥锁的情况永远不能出现,,也就是说,不允许建立Widget对象的“半成品”。如果将RAII惯用法应用于成员对象,那么我们就可以实现这个不变式:

class Widget {public:* myLock): file_(myFile),// 获取文件myFilelock_(myLock)// 获取互斥锁myLock{}// …private:FileHandle file_;LockHandle lock_;};

FileHandle和LockHandle类的对象作为Widget类的数据成员,分别表示需要获取的文件和互斥锁。资源的获取过程就是两个成员对象的初始化过程。在此系统会自动地为我们进行资源管理,程序员不必显式地添加任何异常处理代码。例如,当已经创建完file_,但尚未创建完lock_时,有一个异常被抛出,则系统会调用file_的析构函数,而不会调用lock_的析构函数。Bjarne所谓构造函数和析构函数“与异常处理的交互作用”,说的就是这种情形。

RAII的本质内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源必被释放。换句话说,拥有对象就等于拥有资源,对象存在则资源必定存在。由此可见,RAII惯用法是进行资源管理的有力武器。C++程序员依靠RAII写出的代码不仅简洁优雅,而且做到了异常安全。难怪微软的MSDN杂志在最近的一篇文章中承认:“若论资源管理,谁也比不过标准C++”。

创建自己的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_; };如你想要拥有完美无暇的友谊,可能一辈子找不到朋友

RAII(资源获取即初始化)详解

相关文章:

你感兴趣的文章:

标签云: