1.概念
Resource Acquisition Is Initialization 机制是Bjarne Stroustrup首先提出的。要解决的是这样一个问题:
在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。将初始化和资源释放都移动到一个包装类中的好处:
保证了资源的正常释放
省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
简化代码体积。
2.应用场景
1)文件操作
我们可以是用这个机制将文件操作包装起来完成一个异常安全的文件类。实现上,注意将复制构造函数和赋值符私有化,这个是通过一个私有继承类完成的,因为这两个操作在此并没有意义,当然这并不是RAII所要求的。
/*
* =============================================================================
*
* Filename: file.cpp
*
* Description: RAII for files
*
* Version: 1.0
* Created: 05/09/2011 06:57:43 PM
* Revision: none
* Compiler: g++
*
* Author: gnuhpc (http://blog.csdn.net/gnuhpc), warmbupt@gmail.com
*
* ===============================================================================
*/
#include
#include
#include
using namespace std;
class NonCopyable
{
public:
NonCopyable(){};
private:
NonCopyable (NonCopyable const &); // private copy constructor
NonCopyable & operator = (NonCopyable const &); // private assignment operator
};
class SafeFile:NonCopyable{
public:
SafeFile(const char* filename):fileHandler(fopen(filename,"w+"))
{
if( fileHandler == NULL )
{
throw runtime_error("Open Error!");
}
}
~SafeFile()
{
fclose(fileHandler);
}
void write(const char* str)
{
if( fputs(str,fileHandler)==EOF )
{
throw runtime_error("Write Error!");
}
}
void write(const char* buffer, size_t num)
{
if( num!=0 && fwrite(buffer,num,1,fileHandler)==0 )
{
throw runtime_error("Write Error!");
}
}
private:
FILE *fileHandler;
SafeFile(const SafeFile&);
SafeFile &operator =(const SafeFile&);
};
int main(int argc, char *argv[])
{
SafeFile testVar("foo.test");
testVar.write("Hello RAII");
}C++的结构决定了其原生支持RAII,而在Java 中,对象何时销毁是未知的,所以在Java 中可以使用try-finally做相关处理。
2)智能指针模拟
一个更复杂一点的例子是模拟智能指针,抽象出来的RAII类中实现了一个操作符*,直接返回存入的指针:
现在我们有一个类:
class Example {
SomeResource* p_;
SomeResource* p2_;
public:
Example() :
p_(new SomeResource()),
p2_(new SomeResource()) {
std::cout << "Creating Example, allocating SomeResource! ";
}
Example(const Example& other) :
p_(new SomeResource(*other.p_)),
p2_(new SomeResource(*other.p2_)) {}
Example& operator=(const Example& other) {
// Self assignment
if (this==&other)
return *this;
*p_=*other.p_;
*p2_=*other.p2_;
return *this;
}
~Example() {
std::cout << "Deleting Example, freeing SomeResource! ";
delete p_;
delete p2_;
}
};
假设在创建SomeResource的时候可能会有异常,那么当p_指向的资源被创建但p2_指向的资源创建失败时,Example的实例就整个创建失败,那么p_指向的资源就存在内存泄露问题。
用下边的这个方法可以为权宜之计:
Example() : p_(0),p2_(0)
{
try {
p_=new SomeResource();
p2_=new SomeResource("H",true);
std::cout << "Creating Example, allocating SomeResource! ";
}
catch(...) {
delete p2_;
delete p_;
throw;
}
}
但是我们可以利用一个对象在离开一个域中会调用析构函数的特性,在构造函数中完成初始化,在析构函数中完成清理工作,将需要操作和保护的指针作为成员变量放入RAII中。
template
class RAII {
T* p_;
public:
explicit RAII(T* p) : p_(p) {}
~RAII() {
delete p_;
}
void reset(T* p) {
delete p_;
p_=p;
}
T* get() const {
return p_;
}
T& operator*() const {
return *p_;
}
void swap(RAII& other) {
std::swap(p_,other.p_);
}
private:
RAII(const RAII& other);
RAII& operator