之前 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++ 有提供用 new 和 delete 這種動態記憶體配置的方法,可以很自由地配置、使用程式需要的記憶體。但是在使用 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)就已經都有支援(MSDN,VC11 的說明感覺寫的比較好),可以直接用了~(註 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 吧~
註解:
-
實際上,Smart Pointer 的概念並不是 C++11 才有的,在之前的 C++ STL 裡,其實也已經有提供 auto_ptr 可以使用(參考),不過在 C++11 是建議用 unique_ptr 來取代 auto_ptr;原因可以參考《Using unique_ptr, Part I》這篇文章。
-
如果使用的開發環境所提供的 STL 裡沒有提供 C++11 新的 Smart Pointer 的話,也可以使用 C++ Boost Libraries 所提供的版本,基本上功能應該算是完全相同的,不過還有更多類型。
[…] 2. 避免 memory leak:C++11 Smart Pointer […]
讚讚
[…] 使用 pointer 的形式取代 libAObj 的物件(這邊是用 smart point) […]
讚讚
[…] 這樣的設計比較大的問題,應該就是指標本身的危險性了。 如果這個回傳值需要留著很久的話,就很有可能會難以判斷什麼時候需要釋放了。(當然,使用 smart pointer(參考)或許可以解決問題) […]
讚讚
[…] Smart Pointer 來做資源管理、甚至是透過避免使用指標的方法,來避免 memory […]
讚讚
[…] 如果認真看的話,也會發現其實裡面有的是現有的 STL 就已經有的(smart pointer),也有部分是已經決定未來會納入標準的(std::span、參考)。 […]
讚讚
[…] memory leak:C++11 Smart Pointer》(上、下)這兩篇文章,已經大概介紹了 C++11 的智慧指標(smart pointer)了。而 […]
讚讚