C++之shared_ptr總結
Share_ptr也是一種智慧指標。類比於auto_ptr學習。所以推薦先學習auto_ptr,再來學習shared_ptr。本部落格的前兩個就是auto_ptr的總結。希望感興趣的朋友可以看看。
Shared_ptr和auto_ptr最大的區別就是,shared_ptr解決了指標間共享物件所有權的問題,也就是auto_ptr中的賦值的奇怪問題。所以滿足了容器的要求,可以用於容器中。而auto_ptr顯然禁止共享物件所有權,不可以用於容器中。
int * a=new int(2); shared_ptr<int> sp1(a); shared_ptr<int> sp2(sp1); OK
當然shared_ptr作為一種智慧指標,也擁有和shared_ptr一些相似的性質。它們本質上都是類,但是使用起來像指標。它們都是為了解決防止記憶體洩漏的解決方案。都是運用了RAII技術來實現的。
注意:使用shared_ptr也要引用標頭檔案#include<memory>
由於shared_ptr的原始碼過於複雜,我們不給出原始碼。類比於auto_ptr學習.
1. 首先類shared_ptr有兩個成員變數。T * px和unsign long * pn;
T * px;顯然和auto_ptr一樣,用於儲存物件的指標。
pn用於記錄有多少個shared_ptr擁有同一個物件。pn是shared_ptr物件間共享的,類似於static成員變數。
template<class T>
class shared_ptr{
private:
T *px; // contained pointer
unsignedlong* pn; // reference counter
}
總結:其實shared_ptr的原理,就是使用px來記錄指標,使用*pn來記錄px指向的物件的擁有者share_ptr的個數,當一個shared_ptr物件達到作用域時,不會釋放資源,只有當*pn變為0的時候,才會釋放指標指向的資源。
2. 一個簡單實現的原始碼(仍然看懂原始碼還是最重要的。)
#pragma once //shared_ptr的簡單實現版本 //基於引用記數的智慧指標 //它可以和stl容器完美的配合 namespace boost { template<class T> class shared_ptr { typedef unsigned longsize_type; private: T *px; // contained pointer size_type* pn; // reference counter public: //建構函式---------------------------------------------------2 /* int* a=new int(2); shared_ptr<int> sp; shared_ptr<int> sp(a); */ explicitshared_ptr(T* p=0) : px(p) { pn = new size_type(1); } /* Derived d; shared_ptr<Base> ap(d); */ template<typename Y> shared_ptr(Y* py) { pn = newsize_type(1); px=py; } //copy建構函式------------------------------------------------ /* int * a=new int; shared_ptr<int> sp(a); shared_ptr<int> sp1(sp); */ shared_ptr(constshared_ptr& r) throw(): px(r.px) { ++*r.pn; pn = r.pn; } /* shared_ptr<Derived>sp1(derived); shared_ptr<Base> sp2(sp1); */ template<typename Y> shared_ptr(constshared_ptr<Y>& r)//用於多型 { px = r.px; ++*r.pn; pn = r.pn; //shared_count::op= doesn't throw } //過載賦值operator=-------------------------------------------- shared_ptr& operator=(const shared_ptr& r) throw() { if(this== &r) return *this; dispose(); px = r.px; ++*r.pn; pn = r.pn; return *this; } template<typename Y> shared_ptr& operator=(const shared_ptr<Y>& r)//用於多型 { dispose(); px = r.px; ++*r.pn; pn = r.pn; //shared_count::op= doesn't throw return *this; } ~shared_ptr() { dispose(); } void reset(T* p=0) { if ( px == p ) return; if (--*pn == 0) { delete(px); } else { // allocate newreference // counter // fix: prevent leak if new throws try { pn = new size_type; } catch (...) { // undo effect of —*pn above to // meet effects guarantee ++*pn; delete(p); throw; } // catch } // allocate newreference counter *pn = 1; px = p; } // reset reference operator*()const throw(){ return *px; } pointer operator->()const throw(){ return px; } pointer get() constthrow(){ returnpx; } size_type use_count() constthrow()// { return *pn; } bool unique() const throw()// { return *pn ==1; } private: void dispose() throw() { if (--*pn == 0) { delete px; delete pn; } } }; // shared_ptr template<typename A,typenameB> inline bool operator==(shared_ptr<A>const & l, shared_ptr<B> const & r) { return l.get() == r.get(); } template<typename A,typenameB> inline bool operator!=(shared_ptr<A>const & l, shared_ptr<B> const & r) { return l.get() != r.get(); } }//namespace boost
要注意的地方:
3. Shared_ptr和auto_ptr都有類似的規定:
看看它們的copy構造和過載賦值都可以看出:
不允許
int* a=new int(2);
shared_ptr<int>sp=a;// error
sp=a;// error
就是不允許使用一個純指標給一個智慧指標賦值或copy構造。只能使用智慧指標給另一個智慧指標賦值或copy構造。
int* a=new int(2);
shared_ptr<int> sp(a);//建構函式
shared_ptr<int> sp1(sp);//copy構造
sp1=sp;//賦值
在auto_ptr中也是相同的。
4. 注意shared_ptr的幾個函式
Ø Reset()函式:重置函式
標準中的是:
int* a=new int(2);
int* b=new int(3);
shared_ptr<int> sp2(a);
shared_ptr<int> sp1(a);
shared_ptr<int> sp(a);
sp.reset(b);
sp.reset();
sp.reset(sp2); -----!!!也是可以的。
使得sp獲得b的擁有權。失去a的擁有權。注意這會使得a的擁有者少1.當a的擁有者變為0時,就會釋放a的資源。
Ø Swap()函式:交換函式
int* a=new int(2);
shared_ptr<int> sp(a);
shared_ptr<int> sp1(a);
sp.swap(sp1);
就是兩個shared_ptr中的px和pn都互換一下。
Ø Get()函式:返回px
Ø Use_count函式:返回*pn,就是物件的擁有者的數量。
Ø Unique函式:令*pn=1;讓物件的擁有者的數量變為1。返回bool
Ø 同時share_ptr也過載了*和->
5. tr1中過載了幾個有關shared_ptr的符號:
template<classT, class U>
booloperator==(shared_ptr<T> const& a, shared_ptr<U> const& b);
判斷擁有的物件是否是一樣的
template<classT, class U>
bool operator!=(shared_ptr<T> const&a, shared_ptr<U> const& b);
判斷擁有的物件是否是不一樣的
template<classT, class U>
bool operator<(shared_ptr<T>const& a, shared_ptr<U> const& b);
過載了小於號,在STL中的LIST中非常有用。
int* a=new int(2);
int* b=new int(3);
shared_ptr<int> sp(a);
shared_ptr<int> sp1(b);
if(sp<sp1)
cout<<"2222"<<endl;
6. 注意真實中shared_ptr中沒有public dispose這個函式,這裡只是為了避免程式碼重複。
7. 注意shared_ptr中的解構函式中不是直接釋放資源,而是呼叫了dispose函式,如果*pn==0了,才會釋放資源。
8.shared_ptr的多執行緒的安全性
shared_ptr 本身不是 100%執行緒安全的。它的引用計數本身是安全且無鎖的,但物件的讀寫則不是,因為shared_ptr有兩個資料成員,讀寫操作不能原子化。根據文件,shared_ptr的執行緒安全級別和內建型別、標準庫容器、string一樣,即:
- 一個 shared_ptr 實體可被多個執行緒同時讀取;
- 兩個的 shared_ptr 實體可以被兩個執行緒同時寫入,“析構”算寫操作;
- 如果要從多個執行緒讀寫同一個 shared_ptr 物件,那麼需要加鎖。
發現了兩個非常有意思的東西:
1. 看tr1中的原始碼中發現兩個這樣的東西:
template<class Y, classD> shared_ptr(Y * p, D d);
template<class Y, classD> void reset(Y * p, D d);
其中的D d是個什麼東西?原始碼的解釋是d是一個deleter(刪除器)。至此我們突然發現我們可以給shared_ptr指定一個刪除器,當*pn==0的時候,不去釋放資源,而去呼叫我們自己給它的刪除器。
當shared_ptr的引用次數為0的時候,share_ptr就會呼叫釋放函式來釋放資源。
當我們希望引用次數為0的時候,shared_ptr不釋放資源,而是呼叫我們指定的操作的時候,就會用到D d;
void foo(int * d)
{
cout<<"1234"<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
int* a=new int(2);
shared_ptr<int> sp(a,foo);
shared_ptr<int> sp1(sp);
sp.reset();
sp1.reset();
//_CrtDumpMemoryLeaks();
system("pause");
return 0;
}
注意!:
1. 指定的刪除器的引數必須是int*;和shared_ptr<int>中的int對應。不能是其他的,或者為空也是錯的。因為系統會把shared_ptr的物件px賦給刪除器的引數,我們也可以在刪除器中釋放資源。
2. 只有a的引用次數為0才會呼叫,所以如果沒有sp1.reset()。也不會呼叫foo函式。
2. 使用shared_ptr的時候,要小心,想一想操作的內在含義才去做。
1>
int* a=new int(2);
shared_ptr<int> sp(a);
shared_ptr<int> sp1(sp);
sp.reset();//--------(1)
sp.reset();//--------(2)
這裡(1)是重置了sp,注意(2)是沒有任何作用的,不能使得a的引用次數變為0.想一想reset的函式內部,(2)的時候,sp中的物件pn已經為空了,則不能改變*pn的值了。
2>
int* a=new int(2);
shared_ptr<int> sp(a);//----------(1)
shared_ptr<int> sp1(a);//---------(2)
注意:這裡的(2)也是不對的。想一想shared_ptr的建構函式,(1)的時候,sp的px指向a,且*pn為1.而(2)的時候,px指向a,且*pn也是1.這顯然就問題了。a被引用了2次,但是*pn為1.在最後作用域達到的時候,就會釋放2次記憶體,這就會引發異常。
總結:shared_ptr和auto_ptr的區別。
Shared_ptr有兩個變數,一個記錄物件地址,一個記錄引用次數
Auto_ptr只有一個變數,用來記錄物件地址
Shared_ptr可用多個shared_ptr擁有一個資源。
Auto_ptr只能一個auto_ptr擁有一個資源
Shared_ptr可以實現賦值的正常操作,使得兩個地址指向同一資源
Auto_ptr的賦值很奇怪,源失去資源擁有權,目標獲取資源擁有權
Shared_ptr到達作用域時,不一定會釋放資源。
Auto_ptr到達作用於時,一定會釋放資源。
Shared_ptr存在多執行緒的安全性問題,而auto_ptr沒有。
Shared_ptr可用於容器中,而auto_ptr一般不可以用於容器中。
Shared_ptr可以在建構函式、reset函式的時候允許指定刪除器。而auto_ptr不能。
還有這裡說一句:使用智慧指標(不管shared_ptr還是auto_ptr),都要清除原始碼內部的實現原理,使用起來才不會錯。而且使用的時候,一定要想一想函式內部的實現原理再去使用。切記小心。