C++RAII機制總結介紹

定義

RAII是Resource Acquisition Is Initialization(wiki上面翻譯成 “資源獲取就是初始化–使用類來封裝資源,在構造函數中完成資源的分配和初始化,在析構函數中完成資源的清理,可以保證正確的初始化和資源釋放”)的簡稱,是C++語言的一種管理資源、避免泄漏的慣用法。利用的就是C++構造的對象最終會被銷燬的原則。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機制就可以派上用場了。

優點

  1. 不需要顯式地釋放資源。
  2. 採用這種方式,對象所需的資源在其生命期內始終保持有效。

如何使用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機制來實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章