什麼是RAII?
RAII是Resource Acquisition Is Initialization(wiki上面翻譯成 “資源擷取就是初始化”)的簡稱,是C++語言的一種管理資源、避免洩漏的慣用法。利用的就是C++構造的對象最終會被銷毀的原則。RAII的做法是使用一個對象,在其構造時擷取對應的資源,在對象生命期内控制對資源的通路,使之始終保持有效,最後在對象析構的時候,釋放構造時擷取的資源。
為什麼要使用RAII?
上面說到RAII是用來管理資源、避免資源洩漏的方法。那麼,用了這麼久了,也寫了這麼多程式了,口頭上經常會說資源,那麼資源是如何定義的?在計算機系統中,資源是數量有限且對系統正常運作具有一定作用的元素。比如:網絡套接字、互斥鎖、檔案句柄和記憶體等等,它們屬于系統資源。 由于系統的資源是有限的,我們在程式設計使用系統資源時,都必須遵循一個步驟:
- 申請資源;
- 使用資源;
- 釋放資源。
第一步和第二步缺一不可,因為資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源洩漏。
一個最簡單的例子:
#include <iostream>
using namespace std;
int main(){
int* arr = new int[10];
//業務代碼.....
delete[] arr;
arr = nullptr;
return 0;
}
如果程式很複雜的時候,需要為所有的new 配置設定的記憶體delete掉,導緻效率下降,更可怕的是,程式的可了解性和可維護性明顯降低了,當操作增多時,處理資源釋放的代碼就會越來越多,越來越亂。
如果某一個操作發生了異常而導緻釋放資源的語句沒有被調用,怎麼辦? 這個時候,RAII機制就可以派上用場了
如何使用RAII?
當我們在一個函數内部使用局部變量,當退出了這個局部變量的作用域時,這個變量也就别銷毀了;當這個變量是類對象時,這個時候,就會自動調用這個類的析構函數,而這一切都是自動發生的,不要程式員顯示的去調用完成。這個也太好了,RAII就是這樣去完成的。
由于系統的資源不具有自動釋放的功能,而C++中的類具有自動調用析構函數的功能。如果把資源用類進行封裝起來,對資源操作都封裝在類的内部,在析構函數中進行釋放資源。當定義的局部變量的生命結束時,它的析構函數就會自動的被調用,如此,就不用程式員顯示的去調用釋放資源的操作了。
使用RAII 機制的代碼:
#include <iostream>
using namespace std;
class Array
{
public:
Array()
{
m_Array = new int[10];
cout << "調用構造函數" << endl;
}
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]<<" ";
}
cout << endl;
}
~Array()
{
cout << "~調用析構函數" << endl;
if (m_Array != nullptr)
{
delete[] m_Array;
m_Array = nullptr;
}
}
private:
int* m_Array;
};
int main()
{
Array array;
array.InitArray();
array.ShowArray();
return 0;
}

不使用RAII(沒有使用類的思想)的代碼:
#include <iostream>
using namespace std;
bool OperationA();
bool OperationB();
int main()
{
int* testArray = new int[10];
//使用數組的業務代碼
if (!OperationA())
{
delete[] testArray;
testArray = NULL;
return 0;
}
if (!OperationB())
{
delete[] testArray;
testArray = nullptr;
return 0;
}
delete[] testArray;
testArray = nullptr;
return 0;
}
bool OperationA()
{
//
//業務代碼執行判斷
//
return false;
}
bool OperationB()
{
//
//業務代碼執行判斷
//
return true;
}
上面這個例子沒有多大的實際意義,隻是為了說明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);
}
MyLock(const MyLock&) = delete;
MyLock operator =(const MyLock&) = delete;
};
void DoComplex(MyLock& lock)
{
}
unsigned int __stdcall ThreadFun(PVOID pv)
{
// 利用lock變量構造加鎖,析構解鎖
MyLock lock;
int* para = (int*)pv;
//業務代碼.......
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(nullptr, 0, ThreadFun, (void*)&thread1, 0, nullptr);
handle[1] = (HANDLE)_beginthreadex(nullptr, 0, ThreadFun, (void*)&thread2, 0, nullptr);
WaitForMultipleObjects(2, handle, TRUE, INFINITE);
return 0;
}
這個例子可以說是實際項目的一個模型,當多個程序通路臨界變量時,為了不出現錯誤的情況,需要對臨界變量進行加鎖;上面的例子就是使用的Windows的臨界區域實作的加鎖。