轉載:https://www.jianshu.com/p/b7ffe79498be 什麼是RAII。
RAII是Resource Acquisition Is Initialization(wiki上面翻譯成 “資源擷取就是初始化”)的簡稱,是C++語言的一種管理資源、避免泄漏的慣用法。利用的就是C++構造的對象最終會被銷毀的原則。RAII的做法是使用一個對象,在其構造時擷取對應的資源,在對象生命期內控制對資源的訪問,使之始終保持有效,最後在對象析構的時候,釋放構造時擷取的資源。 為什麼要使用RAII。
上面說到RAII是用來管理資源、避免資源泄漏的方法。那麼,用了這麼久了,也寫了這麼多程式了,口頭上經常會說資源,那麼資源是如何定義的。在電腦系統中,資源是數量有限且對系統正常運行具有一定作用的元素。比如:網路通訊端、互斥鎖、檔案控制代碼和記憶體等等,它們屬於系統資源。由於系統的資源是有限的,就好比自然界的石油,鐵礦一樣,不是取之不盡,用之不竭的,所以,我們在編程使用系統資源時,都必須遵循一個步驟:
1 申請資源;
2 使用資源;
3 釋放資源。
第一步和第二步缺一不可,因為資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源泄漏。
一個最簡單的例子:
#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。
當我們在一個函數內部使用局部變數,當退出了這個局部變數的範圍時,這個變數也就別銷毀了;當這個變數是類對象時,這個時候,就會自動調用這個類的解構函式,而這一切都是自動發生的,不要程式員顯示的去調用完成。這個也太好了,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; // 非常感謝益可達非常犀利的review,詳細可以參加益可達在本文的評論 2014.04.13 m_Array = NULL ; } } private : int *m_Array; }; bool OperationA(); bool OperationB(); int main() { ArrayOperation arrayOp; arrayOp.InitArray(); arrayOp.ShowArray(); return 0;}
不使用RAII(沒有使用類的思想)的代碼
#include <iostream> using namespace std; bool OperationA(); bool OperationB(); int main() { int *testArray = new int [10]; // Here, you can use the array if (!OperationA()) { // If the operation A failed, we should delete the memory delete [] testArray; testArray = NULL ; return 0; } if (!OperationB()) { // If the operation A failed, we should delete the memory delete [] testArray; testArray = NULL ; return 0; } // All the operation succeed, delete the memory delete [] testArray; testArray = NULL ; return 0; } bool OperationA() { // Do some operation, if the operate succeed, then return true, else return false return false ; } bool OperationB() { // Do some operation, if the operate succeed, then return true, else return false 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); }private: MyLock( const MyLock &); MyLock operator =(const MyLock &);};void DoComplex(MyLock &lock ) // 非常感謝益可達犀利的review 2014.04.13{}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函數。
上述的兩個例子都是RAII機制的應用,理解了上面的例子,就應該能理解了RAII機制的使用了。
作者:sexycoder
連結:https://www.jianshu.com/p/b7ffe79498be
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。