1. 程式人生 > >在你的程式碼中使用Boost智慧指標

在你的程式碼中使用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],延遲例項化,操作PIMPLRAII(看下面)

缺點

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 )。

·RAIIResource-Acquisition-Is-Initialization)的慣用手法中。

·執行分割介面。

注意:如果你沒有聽說過PIMPL (a.k.a. handle/body) RAII,可以找一個好的C++書,在C++中處於重要的內容,一般C++程式設計師都應該知道(不過我就是第一次看到這個寫法)。智慧指標只是一中方便的他們的方法,本文中不討論他們的內容。

PIMPL:如果必須包容一個可能拋異常的子物件,但仍然不想從你自己的建構函式中丟擲異常,考慮使用被叫做Handle ClassPimpl的方法(“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*(因為TU的基類),那麼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來指向它們的函式,應該命名一個區域性變數來實現。(這可以使處理以外更安全,有詳細解說)。

引用計數器是一種便利的資源管理機制,它有一個基本回收機制。但迴圈引用不能夠自動回收,計算機很難檢測到。一個最簡單的例子,如下:

struct CDad;

struct CChild;

typedef boost::shared_ptr<CDad>CDadPtr;

typedef boost::shared_ptr<CChild> CChildPtr;

struct CDad : public CSample

{

CChildPtr myBoy;

};

struct CChild : public CSample

{

CDadPtr myDad;

};

// a "thing" that holds a smart pointer to another "thing":

CDadPtrparent(new CDadPtr);

CChildPtr child(new CChildPtr);

// deliberately create a circular reference:

parent->myBoy = child;

child->myDad = dad;

// resetting one ptr...

child.reset();

parent仍然引用CDad物件,它自己本身又引用CChild。整個情況如下圖所示:

如果我們呼叫dad.reset(),那麼我們兩個物件都會失去聯絡。但這種正確的離開這個引用,共享的指標看上去沒有理由去釋放那兩個物件,我們不能夠再訪問那兩個物件,但那兩個物件的確還存在,這是一種非常嚴重的記憶體洩露。如果擁有更多的這種物件,那麼將由更多的臨界資源不能正常釋放。

如果不能解決好共享智慧指標的這種操作,這將是一個嚴重的問題(至少是我們不可接受的)。因此我們需要打破這種迴圈引用,下面有三種方法:

A、當只剩下最後一個引用的時候需要手動打破迴圈引用釋放物件。

B、Dad的生存期超過Child的生存期的時候,Child需要一個普通指標指向Dad

C、使用boost::weak_ptr打破這種迴圈引用。

方法AB並不是一個完美的解決方案,但是可以在不使用weak_ptr的情況下讓我們使用智慧指標,讓我們看看weak_ptr的詳細情況。