C++程序的設(shè)計機制3 RAII機制
RAII(Resource Acquisition Is Initialization )機制是Bjarne Stroustrup首先提出的。要解決的是這樣一個問題:
在C++中,如果在這個程序段結(jié)束時需要完成一些資源釋放工作,那么正常情況下自然是沒有什么問題,但是當(dāng)一個異常拋出時,釋放資源的語句就不會被執(zhí)行。于是Bjarne Stroustrup就想到確保能運行資源釋放代碼的地方就是在這個程序段(棧幀)中放置的對象的析構(gòu)函數(shù)了,因為stack winding會保證它們的析構(gòu)函數(shù)都會被執(zhí)行。將初始化和資源釋放都移動到一個包裝類中的好處:
- 保證了資源的正常釋放
- 省去了在異常處理中冗長而重復(fù)甚至有些還不一定執(zhí)行到的清理邏輯,進而確保了代碼的異常安全。
- 簡化代碼體積。
1、應(yīng)用場景
1)文件操作
我們可以是用這個機制將文件操作包裝起來完成一個異常安全的文件類。實現(xiàn)上,注意將復(fù)制構(gòu)造函數(shù)和賦值符私有化,這個是通過一個私有繼承類完成的,因為這兩個操作在此并沒有意義,當(dāng)然這并不是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++的結(jié)構(gòu)決定了其原生支持RAII,而在Java 中,對象何時銷毀是未知的,所以在Java 中可以使用try-finally做相關(guān)處理。
#p#
2)智能指針模擬
一個更復(fù)雜一點的例子是模擬智能指針,抽象出來的RAII類中實現(xiàn)了一個操作符*,直接返回存入的指針:
現(xiàn)在我們有一個類:
- class Example {
- SomeResource* p_;
- SomeResource* p2_;
- public:
- Example() :
- p_(new SomeResource()),
- p2_(new SomeResource()) {
- std::cout << "Creating Example, allocating SomeResource!\n";
- }
- 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!\n";
- delete p_;
- delete p2_;
- }
- };
假設(shè)在創(chuàng)建SomeResource的時候可能會有異常,那么當(dāng)p_指向的資源被創(chuàng)建但p2_指向的資源創(chuàng)建失敗時,Example的實例就整個創(chuàng)建失敗,那么p_指向的資源就存在內(nèi)存泄露問題。
用下邊的這個方法可以為權(quán)宜之計:
- Example() : p_(0),p2_(0)
- {
- try {
- p_=new SomeResource();
- p2_=new SomeResource("H",true);
- std::cout << "Creating Example, allocating SomeResource!\n";
- }
- catch(...) {
- delete p2_;
- delete p_;
- throw;
- }
- }
但是我們可以利用一個對象在離開一個域中會調(diào)用析構(gòu)函數(shù)的特性,在構(gòu)造函數(shù)中完成初始化,在析構(gòu)函數(shù)中完成清理工作,將需要操作和保護的指針作為成員變量放入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!\n";
- }
- };
現(xiàn)在即使p_成功而p2_失敗,那么在Stack winding時也會調(diào)用RAII的析構(gòu)函數(shù)保證了p_指向的Someresource被析構(gòu)。這種方法較之例1中需要實現(xiàn)被組合的指針類型相應(yīng)的接口不同,這里不需要對接口進行封裝。當(dāng)然,在例1中,你也可以提供一個getPointer的函數(shù)直接將句柄提供出來。
其實在Example中,已經(jīng)不需要析構(gòu)函數(shù)了,因為RAII類會幫它照顧好這一切的。這有點像auto_ptr,本文并不打算深入討論智能指針這個話題。
#p#
3)鎖操作
- /*
- * =====================================================================================
- *
- * Filename: threadlock.cpp
- *
- * Description: Lock for RAII
- *
- * Version: 1.0
- * Created: 05/09/2011 10:16:13 PM
- * Revision: none
- * Compiler: g++
- *
- * Author: gnuhpc (http://blog.csdn.net/gnuhpc), warmbupt@gmail.com
- *
- * =====================================================================================
- */
- #include
- #include
- #include
- int counter = 0;
- void* routine(void *ptr);
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- class NonCopyable
- {
- public:
- NonCopyable(){};
- private:
- NonCopyable (NonCopyable const &); // private copy constructor
- NonCopyable & operator = (NonCopyable const &); // private assignment operator
- };
- class ScopeMutex:NonCopyable
- {
- public:
- ScopeMutex(pthread_mutex_t* mutex):mutex_(mutex){
- pthread_mutex_lock( mutex_ );
- }
- ~ScopeMutex(){
- pthread_mutex_unlock( mutex_ );
- }
- private:
- pthread_mutex_t *mutex_;
- };
- int main(int argc, char *argv[])
- {
- int rc1, rc2;
- pthread_t thread1, thread2;
- if( (rc1=pthread_create( &thread1, NULL, routine, NULL)) )
- {
- printf("Thread creation failed: %d\n", rc1);
- }
- if( (rc2=pthread_create( &thread2, NULL, routine, NULL)) )
- {
- printf("Thread creation failed: %d\n", rc1);
- }
- pthread_join( thread1, NULL);
- pthread_join( thread2, NULL);
- }
- void* routine(void *ptr)
- {
- ScopeMutex scopeMutex(&mutex);
- counter++;
- printf("%d\n",counter);
- }
2.總結(jié)
RAII機制保證了異常安全,并且也為程序員在編寫動態(tài)分配內(nèi)存的程序時提供了安全保證。缺點是有些操作可能會拋出異常,如果放在析構(gòu)函數(shù)中進行則不能將錯誤傳遞出去,那么此時析構(gòu)函數(shù)就必須自己處理異常。這在某些時候是很繁瑣的。
【編輯推薦】