在你的程式碼中使用Boost智慧指標
2、首先介紹:boost::scoped_ptr<T>
scoped_ptr 是 Boost 提供的一個簡單的智慧指標,它能夠保證在離開作用域後物件被釋放。
例子說明:本例子使用了一個幫助我們理解的類: CSample, 在類的建構函式、賦值函式、解構函式中都加入了列印除錯語句。因此在程式執行的每一步都會列印除錯資訊。在例子的目錄裡已經包含了程式中需要的Boost庫的部分內容,不需要下載其它內容(檢視Boost的安裝指南)。
下面的例子就是使用scoped_ptr 指標來自動釋放物件的:
使用普通指標 |
使用scoped_ptr 指標 |
void Sample1_Plain() { CSample * pSample(new CSample); if (!pSample->Query() ) // just some function... { delete pSample; return; } pSample->Use(); delete pSample; } |
#include "boost/smart_ptr.h" void Sample1_ScopedPtr() { boost::scoped_ptr<CSample> samplePtr(new CSample); if (!samplePtr->Query() ) // just some function... return; samplePtr->Use(); } |
使用普通普通指標的時候,我們必須記住在函式退出的時候要釋放在這個函式內建立的物件。當我們使用例外的時候處理指標是特別煩人的事情(容易忘記銷燬它)。使用scoped_ptr 指標就能夠在函式結束的時候自動銷燬它,但對於函式外建立的指標就無能為力了。
優點:對於在複雜的函式種,使用scoped_ptr 指標能夠幫助我們處理那些容易忘記釋放的物件。也因此在除錯模式下如果使用了空指標,就會出現一個斷言。
優點 |
自動釋放本地物件和成員變數[1],延遲例項化,操作PIMPL和RAII(看下面) |
缺點 |
在STL容器裡,多個指標操作一個物件的時候需要注意。 |
效能 |
使用scoped_ptr 指標,會增加一個普通指標。 |
引用指標計數器記錄有多少個引用指標指向同一個物件,如果最後一個引用指標被銷燬的時候,那麼就銷燬物件本身。
shared_ptr 就是Boost中普通的引用指標計數器,它表示可以有多個指標指向同一個物件,看下面的例子:
void Sample2_Shared() { // (A)建立Csample類的一個例項和一個引用。 boost::shared_ptr<CSample> mySample(new CSample); printf("The Sample now has %i references/n", mySample.use_count()); // The Sample now has 1 references // (B)付第二個指標給它。 boost::shared_ptr<CSample> mySample2 = mySample; // 現在是兩個引用指標。 printf("The Sample now has %i references/n", mySample.use_count()); // (C) 設定第一個指標為空。 mySample.reset(); printf("The Sample now has %i references/n", mySample2.use_count());// 一個引用 // 當mySample2離開作用域的時候,物件只有一個引用的時候自動被刪除。 } |
在(A)中在堆疊重建立了CSample類的一個例項,並且分配了一個shared_ptr指標。物件mySample入下圖所示:
然後我們分配了第二個指標mySample2,現在有兩個指標訪問同一個資料。
我們重置第一個指標(將mySample設定為空),程式中仍然有一個Csample例項,mySample2有一個引用指標。
只要當最有一個引用指標mySample2退出了它的作用域之外,Csample這個例項才被銷燬。
當然,並不僅限於單個Csample這個例項,或者是兩個指標,一個函式,下面是用shared_ptr的例項:
·用作容器中。
·用在PIMPL的慣用手法 (the pointer-to-implementation idiom )。
·RAII(Resource-Acquisition-Is-Initialization)的慣用手法中。
·執行分割介面。
注意:如果你沒有聽說過PIMPL (a.k.a. handle/body) 和 RAII,可以找一個好的C++書,在C++中處於重要的內容,一般C++程式設計師都應該知道(不過我就是第一次看到這個寫法)。智慧指標只是一中方便的他們的方法,本文中不討論他們的內容。
PIMPL:如果必須包容一個可能拋異常的子物件,但仍然不想從你自己的建構函式中丟擲異常,考慮使用被叫做Handle Class或Pimpl的方法(“Pimpl”個雙關語:pImpl或“pointer to implementation”)
boost::shared_ptr 有一些重要的特徵必須建立在其它操作之上。
·shared_ptr<T>作用在一個未知型別上
當宣告或定義一個shared_ptr<T>,T可能是一個未知的型別。例如你僅僅在前面聲明瞭class T,但並沒有定義class T。當我們要釋放這個指標的時候我們需要知道這個T具體是一個宣告型別。
·shared_ptr<T>作用在任意型別上
在這裡本質上不需要制定T的型別(如從一個基類繼承下來的)
·shared_ptr<T>支援自己定義釋放物件的操作
如果你的類中自己寫了釋放方法,也可以使用。具體參照Boost文件。
·強制轉換
如果你定義了一個U*能夠強制轉換到T*(因為T是U的基類),那麼shared_ptr<U>也能夠強制轉換到shared_ptr<T>。
·shared_ptr 是執行緒安全的
(這種設計的選擇超過它的優點,在多執行緒情況下是非常必要的)
·已經作為一種慣例,用在很多平臺上,被證明和認同的。
5 例子:在容器中使用shared_ptr
許多容器類,包括STL,都需要拷貝操作(例如,我們插入一個存在的元素到list,vector,或者container。)當拷貝操作是非常銷燬資源的時候(這些操作時必須的),典型的操作就是使用容器指標。
std::vector<CMyLargeClass *> vec; vec.push_back( new CMyLargeClass("bigString") ); |
將記憶體管理的任務拋給呼叫者,我們能夠使用shared_ptr來實現。
typedef boost::shared_ptr<CMyLargeClass>CMyLargeClassPtr; std::vector<CMyLargeClassPtr> vec; vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) ); |
當vector被銷燬的時候,這個元素自動被銷燬了。當然,除非有另一個智慧指標引用了它,則還本能被銷燬。讓我們看Sample3中的使用:
void Sample3_Container() { typedef boost::shared_ptr<CSample> CSamplePtr; // (A) create a container of CSample pointers: std::vector<CSamplePtr> vec; // (B) add three elements vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample)); // (C) "keep" a pointer to the second: CSamplePtr anElement = vec[1]; // (D) destroy the vector: vec.clear(); // (E) the second element still exists anElement->Use(); printf("done. cleanup is automatic/n"); // (F) anElement goes out of scope, deleting the last CSample instance } 6、使用Boost中的智慧指標,什麼是正確的使用方法 使用智慧指標的一些操作會產生錯誤(突出的事那些不可用的引用計數器,一些物件太容易釋放,或者根本釋放不掉)。Boost增強了這種安全性,處理了所有潛在存在的危險,所以我們要遵循以下幾條規則使我們的程式碼更加安全。 下面幾條規則是你應該必須遵守的: 規則一:賦值和儲存——對於智慧指標來說,賦值是立即建立一個例項,並且儲存在那裡。現在智慧指標擁有一個物件,你不能手動釋放它,或者取走它,這將幫助你避免意外地釋放了一個物件,但你還在引用它,或者結束一個不可用的引用計數器。 規則二:_ptr<T>不是T*——恰當地說,不能盲目地將一個T* 和一個智慧指標型別T相互轉換。意思是: ·當建立一個智慧指標的時候需要明確寫出__ptr<T> myPtr<new T>。 ·不能將T*賦值給一個智慧指標。 ·不能寫ptr = NULL,應該使用ptr.reset()。 ·重新找回原始指標,使用ptr.get(),不必釋放這個指標,智慧指標會去釋放、重置、賦值。使用get()僅僅通過函式指標來獲取原始指標。 ·不能通過T*指向函式指標來代表一個__ptr<T>,需要明確構造一個智慧指標,或者說將一個原始指標的所有權給一個指標指標。(見規則三) 規則三:非迴圈引用——如果有兩個物件引用,而他們彼此都通過一個一個引用指標計數器,那麼它們不能釋放,Boost 提供了weak_ptr來打破這種迴圈引用(下面介紹)。 規則四:非臨時的share_ptr ——不能夠造一個臨時的share_ptr來指向它們的函式,應該命名一個區域性變數來實現。(這可以使處理以外更安全,有詳細解說)。 引用計數器是一種便利的資源管理機制,它有一個基本回收機制。但迴圈引用不能夠自動回收,計算機很難檢測到。一個最簡單的例子,如下:
parent仍然引用CDad物件,它自己本身又引用CChild。整個情況如下圖所示: 如果我們呼叫dad.reset(),那麼我們兩個物件都會失去聯絡。但這種正確的離開這個引用,共享的指標看上去沒有理由去釋放那兩個物件,我們不能夠再訪問那兩個物件,但那兩個物件的確還存在,這是一種非常嚴重的記憶體洩露。如果擁有更多的這種物件,那麼將由更多的臨界資源不能正常釋放。 如果不能解決好共享智慧指標的這種操作,這將是一個嚴重的問題(至少是我們不可接受的)。因此我們需要打破這種迴圈引用,下面有三種方法: A、當只剩下最後一個引用的時候需要手動打破迴圈引用釋放物件。 B、當Dad的生存期超過Child的生存期的時候,Child需要一個普通指標指向Dad。 C、使用boost::weak_ptr打破這種迴圈引用。 方法A和B並不是一個完美的解決方案,但是可以在不使用weak_ptr的情況下讓我們使用智慧指標,讓我們看看weak_ptr的詳細情況。 |