定義
RAII是Resource Acquisition Is Initialization(wiki上面翻譯成 “資源獲取就是初始化–使用類來封裝資源,在構造函數中完成資源的分配和初始化,在析構函數中完成資源的清理,可以保證正確的初始化和資源釋放”)的簡稱,是C++語言的一種管理資源、避免泄漏的慣用法。利用的就是C++構造的對象最終會被銷燬的原則。RAII的做法是使用一個對象,在其構造時獲取對應的資源,在對象生命期內控制對資源的訪問,使之始終保持有效,最後在對象析構的時候,釋放構造時獲取的資源。
背景
我們在編程使用系統資源(如內存、文件句柄、網絡連接、互斥量、指針等等)時,都必須遵循一個步驟:
- 申請資源;
- 使用資源;
- 釋放資源。
第一步和第三步缺一不可,因爲資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源泄漏。
一個最簡單的例子:
#include <iostream>
using namespace std;
int main()
{
int *testArray = new int [10];
// Here, you can use the array
delete [] testArray;
testArray = NULL ;
return 0;
}
但是如果程序很複雜的時候,需要爲所有的new 分配的內存delete掉,導致極度臃腫,效率下降,更可怕的是,程序的可理解性和可維護性明顯降低了,當操作增多時,處理資源釋放的代碼就會越來越多,越來越亂。如果某一個操作發生了異常而導致釋放資源的語句沒有被調用,怎麼辦?這個時候,RAII機制就可以派上用場了。
優點
- 不需要顯式地釋放資源。
- 採用這種方式,對象所需的資源在其生命期內始終保持有效。
如何使用RAII?
由於系統的資源不具有自動釋放的功能,而C++中的類具有自動調用析構函數的功能。如果把資源用類進行封裝起來,對資源操作都封裝在類的內部,在析構函數中進行釋放資源。當定義的局部變量的生命結束時,它的析構函數就會自動的被調用,如此,就不用程序員顯示的去調用釋放資源的操作了。
使用RAII 機制的代碼:
#include <iostream>
using namespace std;
class ArrayOperation
{
public :
ArrayOperation()
{
m_Array = new int [10];
}
void InitArray()
{
for (int i = 0; i < 10; ++i)
{
*(m_Array + i) = i;
}
}
void ShowArray()
{
for (int i = 0; i <10; ++i)
{
cout<<m_Array[i]<<endl;
}
}
~ArrayOperation()
{
cout<< "~ArrayOperation is called" <<endl;
if (m_Array != NULL )
{
delete[] m_Array;
m_Array = NULL ;
}
}
private :
int *m_Array;
};
bool OperationA();
bool OperationB();
int main()
{
ArrayOperation arrayOp;
arrayOp.InitArray();
arrayOp.ShowArray();
return 0;
}
上面這個例子沒有多大的實際意義,只是爲了說明RAII的機制問題。下面說一個具有實際意義的例子:
#include <iostream>
#include <windows.h>
#include <process.h>
using namespace std;
CRITICAL_SECTION cs;
int gGlobal = 0;
class MyLock
{
public:
MyLock()
{
EnterCriticalSection(&cs);
}
~MyLock()
{
LeaveCriticalSection(&cs);
}
private:
MyLock( const MyLock &);
MyLock operator =(const MyLock &);
};
void DoComplex(MyLock &lock )
{
}
unsigned int __stdcall ThreadFun(PVOID pv)
{
MyLock lock;
int *para = (int *) pv;
// I need the lock to do some complex thing
DoComplex(lock);
for (int i = 0; i < 10; ++i)
{
++gGlobal;
cout<< "Thread " <<*para<<endl;
cout<<gGlobal<<endl;
}
return 0;
}
int main()
{
InitializeCriticalSection(&cs);
int thread1, thread2;
thread1 = 1;
thread2 = 2;
HANDLE handle[2];
handle[0] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread1, 0, NULL );
handle[1] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread2, 0, NULL );
WaitForMultipleObjects(2, handle, TRUE , INFINITE );
return 0;
}
這個例子可以說是實際項目的一個模型,當多個進程訪問臨界變量時,爲了不出現錯誤的情況,需要對臨界變量進行加鎖;上面的例子就是使用的Windows的臨界區域實現的加鎖。
但是,在使用CRITICAL_SECTION時,EnterCriticalSection和LeaveCriticalSection必須成對使用,很多時候,經常會忘了調用LeaveCriticalSection,此時就會發生死鎖的現象。當我將對CRITICAL_SECTION的訪問封裝到MyLock類中時,之後,我只需要定義一個MyLock變量,而不必手動的去顯示調用LeaveCriticalSection函數。
這裏只是簡單舉個例子來說明RALL。RALL機制便是通過利用對象的自動銷燬,使得資源也具有了生命週期,有了自動銷燬(自動回收)的功能。
更多的如智能指針,lock_guard都利用了RALL機制來實現。