天天看點

詳解C++11 RAII機制

什麼是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;
}      
詳解C++11 RAII機制

不使用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;
}      
詳解C++11 RAII機制

這個例子可以說是實際項目的一個模型,當多個程序通路臨界變量時,為了不出現錯誤的情況,需要對臨界變量進行加鎖;上面的例子就是使用的Windows的臨界區域實作的加鎖。

繼續閱讀