1. 程式人生 > >智慧指標 shared_ptr

智慧指標 shared_ptr

shared_ptr:
 shared_ptr是一個最像指標的"智慧指標".
 shared_ptr與scoped_ptr一樣包裝了new操作符在堆上分配的動態物件,但它實現的是引用計數型的智慧指標,可以被自由的拷貝和賦值,在任意的地方共享它,當沒有程式碼使用(引用計數為0)它時才能刪除被包裝的動態分配的物件。shared_ptr也可以安全地放到標準容器中,並彌補了auto_ptr因為轉移語義而不能把指標做為STL容器元素的缺陷。

操作函式:
與scoped_ptr相同特徵:
shared_ptr與scoped_ptr同樣是用於管理new動態分配物件的智慧指標,因此功能上有很多相似之處,它們都過載了*和->操作符以模仿原始指標的行為,提供隱式bool型別轉換以判斷指標的有效性,get()可以得到原始指標,並且沒有提供指標算術操作。
與scoped_ptr不同特徵:
 shared_ptr可以被安全共享的,它是一個全功能的類,有著正常的拷貝,賦值語義,也可以進行shared_ptr間的比較,是“最智慧”的智慧指標。

shared_ptr有多種形式的建構函式,應用於各種可能的情形:
 【1】無引數的shared_ptr()建立一個持有空指標的shared_ptr;
 【2】shared_ptr(Y *p)獲得指向型別T的指標p的管理權,同時引用計數置為1,這個建構函式要求T型別必須能夠轉換為T型別。
 【3】shared_ptr(shared_ptr const & r)從另外一個shared_ptr獲得指標的管理權,同時引用計數加1,結果是兩個shared_ptr共享一個指標的管理權。
 【4】shared_ptr(std::auto_ptr<Y> & r)從一個auto_ptr獲得指標的管理權,引用計數置為1,同時auto_ptr自動失去管理權。
 【5】operator= 賦值操作符可以從另外一個shared_ptr或auto_ptr獲得指標的管理權,其行為同構造函式;
 【6】shared_ptr(Y *p,D d)行為類似shared_ptr(Y *p),但使用引數d指定了析構時的定製刪除器,而不是簡單的delete。

 shared_ptr的reset()函式的行為與scoped_ptr也不盡相同,它的作用是將引用計數減1,停止對指標的共享,除非引用計數為0,否則不會發送刪除操作,帶引數的reset()則類似相同形式的建構函式,原指標引用計數減1的同時改為管理另一個指標。
 
 shared_ptr有兩個專門的函式來檢查引用計數,unique()在shared_ptr是指標的唯一所有者時返回true(這時shared_ptr的行為類似auto_ptr或者scoped_ptr),use_count()返回當前指標的引用計數,要小心,use_count()應該僅僅用於測試或者除錯,它不提供高效率的操作,而且有的時候可能是不可用的(少數情形)。而unique()則是可靠的,任何時候都可用,而且比use_count()==1速度更快.


 shared_ptr還支援比較運算,可用測試兩個shared_ptr的相等或不相等,比較基於內部儲存的指標,相當於a.get() == b.get().shared_ptr還可以使用operator<比較大小,同樣基於內部儲存的指標,但不提供除operator<以外的比較操作符,這使得shared_ptr可以被用於標準關聯容器(set和map);
 在編寫基於虛擬函式的多型程式碼時指標的型別轉換很有用,比如把一個基類指標轉型為一個子類指標或者反過來,但對於shared_ptr不能使用諸如static_cast<T*>(p.get())的形式,這將導致轉型後的指標無法再被shared_ptr正確管理,為了支援這樣的用法,shared_ptr提供了類似的轉型函式static_pointer_cast<T>(),const_pointer_cast<T>()和dynamic_pointer_cast<T>(),它們與標準的轉型操作符static_cast<T>,const_cast<T>和dynamic_cast<T>類似,但返回的是轉型後的shared_ptr.

 shared_ptr還支援流輸出操作符operator<<,輸出內部的指標的值,方便除錯。

用法:
 shared_ptr也提供基本的執行緒安全保證,一個shared_ptr可以被多個執行緒安全讀取,但其他的訪問形式結果是未定義的.

工廠函式:
 shared_ptr在標頭檔案<boost/make_shared.hpp>中提供了一個自由工廠函式(位於boost名字空間)make_shared<T>(),在消除顯式的new呼叫,它的名字模仿了標準庫的make_pair();
 template<class T, class... Args>
 shared_ptr<T> make_shared(Args && ... args);
make_shared()函式可以接受最多10個引數,然後把它們傳遞給型別T的建構函式,建立一個shared_ptr<T>的物件並返回。make_shared()函式要比直接建立shared_ptr物件方式快且高效,因為它的內部僅分配一次記憶體,消除了shared_ptr構造時的開銷.
程式碼示範:
#include <iostream>
#include <vector>
#include <boost/make_shared.hpp>
using namespace boost;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
 shared_ptr<string> sp = make_shared<string>("make_shared");//建立string的共享指標
 shared_ptr<vector<int>> spv = make_shared<vector<int>>(10,2);//建立vector的共享指標
 assert(spv->size() == 10);
 return 0;
}
除了make_shared(),smart_ptr庫還提供了一個allocate_shared(),它比make_shared()多接受一個定製的記憶體分配器型別引數.

應用於標準容器:
有兩種方式可以將shared_ptr應用於標準容器(或者容器介面卡等其他容器).
 一種用法是將容器作為shared_ptr管理物件,如shared_ptr<list<T>>,使容器可以被安全的共享,用法與普通shared_ptr沒有區別。
 另一種用法是將shared_ptr作為容器的元素,如vector<shared_ptr<T>>,因為shared_ptr支援拷貝語義和比較操作,符合標準容器對元素的要求,所以可以實現在容器中安全的容納元素的指標而不是拷貝。
 標準容器不能容納auto_ptr,這是c++標準特別規定的。
 標準容器不能容納scoped_ptr,因為scoped_ptr不能拷貝和賦值
 標準容器可以容納原始指標,但這就喪失了容器的許多好處,因為標準容器無法自動管理型別為指標的元素,必須編寫額外的大量程式碼來保證指標最終被正確刪除
 儲存shared_ptr的容器與儲存原始指標的容器功能幾乎一樣,但shared_ptr為程式設計師做了指標的管理工作,可以任意使用shared_ptr而不用擔心資源洩露.
程式碼示範:
#include <iostream>
#include <vector>
#include <boost/make_shared.hpp>
using namespace boost;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
 typedef vector<shared_ptr<int>> vs;//一個特有shared_ptr的標準容器型別
 vs v(10);       //宣告一個擁有10個元素的容器,元素被
          //初始化為空指標
 int i = 0;
 for (vs::iterator pos = v.begin();pos != v.end(); ++pos)
 {
  (*pos) = make_shared<int>(++i);//使用工廠函式賦值
  cout <<*(*pos) << ",";
 }
 cout <<endl;
 shared_ptr<int> p = v[9];
 *p = 100;
 cout<< *v[9] <<endl;
 return 0;
}

橋接模式:
 橋接模式(bridge)是一種結構型設計模式,它把類的具體實現細節對使用者隱藏起來,以達到類之間的最小耦合關係。在具體程式設計實踐中橋接模式也被稱為pimpl或者handle/body慣用法,它可以將標頭檔案的依賴關係將到最小,減少編譯時間,而且可以不使用虛擬函式實現多型.

應用於工廠模式:
 工程模式是一種建立型設計模式,這個模式包裝了new操作符的使用,使物件的建立工作集中在工廠類或者工廠函式中,從而更容易適應變化,make_shared()就是工廠模式的一個很好的例子。

定製刪除器:
 shared_ptr(Y *p, D d)的第一個引數是要被管理的指標,它的含義與其他建構函式的引數相同,而第二個刪除器引數d則告訴shared_ptr在析構時不是使用delete在操作指標p,而要用d來操作,即把delete p 換成d(p);
 在這裡刪除器d可以是一個函式物件,也可以是一個函式指標,只要它能夠像函式那樣被呼叫,使得d(p)成立即可。對刪除器的要求是它必須是可拷貝的,行為必須也像delete那樣,不能丟擲異常。
 有了刪除器的概念,就可以用shared_ptr實現管理任意資源,只要這種資源提供了它自己的釋放操作,shared_ptr就能夠保證自動釋放。
 shared_ptr的刪除器在處理某些特殊資源時非常有用,它使得使用者可以定製,擴充套件shared_ptr的行為,使shared_ptr不僅僅能夠管理記憶體資源,而是成為一個"萬能"的資源管理工具.


高階議題:
 shared_ptr<void>
 shared_ptr<void>能夠儲存void*型的指標,而void*型指標可以指向任意型別,因此shared_ptr<void>就像是一個泛型的指標容器,擁有容納任意型別的能力。

刪除器的高階用法:
 基於shared_ptr<void>和定製刪除器,shared_ptr可以有更驚人的用法。由於空指標可以使任何指標型別,因此shared_ptr<void>還可以實現退出作用域時呼叫任意函式,例如:
void any_func(void* p)//一個可執行任意功能的函式
{
 cout << "some operate" <<endl;
}
int main()
{
 shared_ptr<void> p((void*)0,any_func);//容納空指標,定製刪除器
     //退出作用域時將執行any_func()
}

shared_ptr<void>儲存了一個空指標,並指定了刪除器是操作void*的一個函式,因此當它析構時會自動呼叫函式any_func(),從而執行任意我們想做的工作。