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), [email protected]
*
* ===============================================================================
*/
#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=(const RAII& other);
};
我們在具體使用把保護的指針Someresource放在RAII中:
class Example {
RAII p_;
RAII p2_;
public:
Example() :
p_(new SomeResource()),
p2_(new 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!
";
}
};
現在即使p_成功而p2_失敗,那麼在Stack winding時也會調用RAII的析構函數保證了p_指向的Someresource被析構。這種方法較之例1中需要實現被組合的指針類型相應的接口不同,這裡不需要對接口進行封裝。當然,在例1中,你也可以提供一個getPointer的函數直接將句柄提供出來。
其實在Example中,已經不需要析構函數了,因為RAII類會幫它照顧好這一切的。這有點像auto_ptr,本文並不打算深入討論智能指針這個話題。
3)鎖操作
/*
* ================================================================================
*
&