概念
使用局部对象管理资源的技术通常称为“资源获取就是初始化”
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{
};
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(