避免 memory leak:C++11 Smart Pointer(上)

| | 0 Comments| 14:59
Categories:

之前 Heresy 已經有介紹過 C 的新標準、C 11 了~而更早之前,Heresy 也有針對 Visual C 10 所支援的 C 0x Core Language 的新功能,做了一些介紹,有興趣的可以回去參考《C 語法再加強:C 0x》一文。而這一篇呢,則是來講一下 C 11 裡、STL 裡的新東西:「General-purpose Smart Pointers」~

所謂「Smart Pointer」是幹嘛的呢?基本上,他是一種用來模擬傳統的 pointer、提供一些附加功能的特殊資料型別;比較常見的功能,主要就是透過自動資源回收(automatic garbage collection)的機制、來進行記憶體管理了~

Memory Leak

為什麼要做這件事呢?主要一點,就是 C 有提供用 newdelete 這種動態記憶體配置的方法,可以很自由地配置、使用程式需要的記憶體。但是在使用 new 來配置記憶體的時候,是需要非常小心的!因為他所配置出來的記憶體空間,不像一般的變數一樣,會在生命周期結束的時候自動把資源釋放掉,除非自己使用 delete 來釋放,不然到程式結束之前,記憶體空間都會一直佔在那邊。

而如果 new 出來了之後、在沒有指標去指到那塊記憶體空間、又沒有做對應的 delete 的情況下,就會產生「記憶體還是佔在那邊,但是卻沒有辦法使用、也沒辦法釋放」的問題,也就是所謂的「memory leak」(維基百科)。

例如下面就是一個 memory leak 的例子:

void MemoryAlloc()
{
int* a = new int(0);
}

當呼叫 MemoryAlloc() 這個函式的時候,在函式內部就會動態配置一塊記憶體空間,透過 a 這個指標拿來使用。但是在函式結束後,a 這個指標就因為生命週期的關係而自動消失、無法再使用了,但是他所指到的記憶體空間,卻還是佔在那邊!此時由於已經沒有指標指到那塊記憶體空間了,所以不但沒辦法使用那塊記憶體空間、連要釋放掉都釋放不了…

當然,這個狀況其實很好解,只要在 MemoryAlloc() 裡,記得加上一行 delete a; 就可以了,但是有的時候卻沒有那麼簡單。例如,當一個函式會回傳一個 pointer 的時候,其實有的時候會很難判斷到底要由誰來做 delete 的動作…下面是一個例子:

class DataGenerator
{
public:
int* GetData();
};

DataGenerator 裡,有一個成員函式 int* GetData(),會傳出來一個 int 的指標;看起來好像沒有什麼大問題?但是在使用的時候,卻有一個問題,那就是到底應不應該在外不去釋放這個指標所指到的記憶體空間?例如下面這樣的程式,就會是一個例子:

DataGenerator DataGen;
int* A = DataGen.GetData();
delete A; // Should do this?

因為實際上,除非文件有很明確地說明這個函式所回傳的記憶體空間不會在 DataGenerator 內部再被用到、需要在外部做釋放,不然其實在外部用 delete 去釋放這塊記憶體空間,其實是很危險的一件事…因為搞不好在外面把他釋放掉後,裡面又跑去使用這塊已經被釋放的記憶體空間,這時候程式就會出問題了。

此外,也還有很多狀況,都有可能會產生「不確定該在哪邊 delete」的問題;例如有多個指標都只到同一塊記憶體空間的時候,也有可能會很難確認到底什麼時候該 delete 他。

C 11 的 Smart Pointer

而為了要解決這類的問題,C 就在 STL 裡面,引進了「Smart Point」的概念(註 1),用來取代指標做的動態配置的資源管理。在 C 11 的 STL 裡,針對使用需求的不同,提供了三種不同的 Smart Pointer,分別是:

  • unique_ptr [MSDN]

    確保一份資源(被配置出來的記憶體空間)只會被一個 unique_ptr 物件管理的 smart pointer;當 unique_ptr 物件消失時,就會自動釋放資源。

  • shared_ptr [MSDN]

    可以有多個 shared_ptr 共用一份資源的 smart pointer,內部會記錄這份資源被使用的次數(reference counter),只要還有 shared_ptr 物件的存在、資源就不會釋放;只有當所有使用這份資源的 shared_ptr 物件都消失的時候,資源才會被自動釋放。

  • weak_ptr [MSDN]

    搭配 shared_ptr 使用的 smart pointer,和 shared_ptr 的不同點在於 weak_ptr 不會影響資源被使用的次數,也就是說的 weak_ptr 存在與否不代表資源會不會被釋放掉,

這些 smart pointer 都是 template class 的形式,所以適用範圍很廣泛;他們都是被定義在 <memory> 這個 header 檔裡、在 std 這個 namespace 下,如果要使用的話,要記得 include 這個 header 檔。以 Microsoft Visual C 來說,在 VC2010(VC10)就已經都有支援(MSDNVC11 的說明感覺寫的比較好),可以直接用了~(註 2)

而他們的使用也都相當簡單,基本上只有在宣告的時候要稍微改一下,其他使用方法都是幾乎不用改變的~這些 smart pointer 都有定義 operator*operator->,所以可以把他們當作一般的 pointer 來操作。例如一般的 pointer 大概會是這樣使用:

int* a = new int(0);  // allocate memory
int b = *a; // dereference
delete a; // release resource

而使用 smart pointer 的話,則會變成是:

unique_ptr<int> a( new int(0) );
int b = *a;

基本上,就是宣告的方法要做修改,同時也不需要特別去 delete 而已。

而如果是 class 的成員函式的話,基本上改用 smart pointer 的時候,使用方法基本上也是不需要改變的。例如下面是一般的 pointer 的寫法:

vector<int>* pVec = new vector<int>();
pVec->push_back( 1 );
cout << pVec->size() << endl;
delete pVec;

下面則是改用 unique_ptr 的寫法:

unique_ptr< vector<int> > pVec( new vector<int>() );
pVec->push_back( 1 );
cout << pVec->size() << endl;

基本上,在使用上也是一樣,除了宣告和初始化的方法不一樣外,使用上不太需要修改什麼就可以直接用了~如果真的有需要,也可以透過 get() 這個函式來取得本來的指標來進行操作,不過這樣就有點失去使用 smart pointer 的目的就是了。

而如果是要把函式內動態配置的資源傳出來的話,使用 smart pointer 也就不用考慮到該由誰來 delete 的問題,而可以丟給 smart pointer 自己去管理了~

shared_ptr<int> MemoryAlloc()
{
shared_ptr<int> a( new int(0) );

return a;
}

由於這些 smart pointer 會做一定程度的自動資源管理,不用像本來使用動態記憶體配置的時候,要去刻意透過 delete 來做記憶體空間的釋放,所以在使用 smart pointer 的時候,可以用比較簡單的方法,來避免 memory leak、或是不知道該由誰來釋放記憶體空間的問題了~

簡介大概就這樣,下一篇,就比較詳細地來介紹一下這三種 smart pointer 吧~


註解:
  1. 實際上,Smart Pointer 的概念並不是 C 11 才有的,在之前的 C STL 裡,其實也已經有提供 auto_ptr 可以使用(參考),不過在 C 11 是建議用 unique_ptr 來取代 auto_ptr;原因可以參考《Using unique_ptr, Part I》這篇文章。

  2. 如果使用的開發環境所提供的 STL 裡沒有提供 C 11 新的 Smart Pointer 的話,也可以使用 C Boost Libraries 所提供的版本,基本上功能應該算是完全相同的,不過還有更多類型。

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *