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);
- hared_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),都要清除源碼內部的實現原理,使用起來纔不會錯。而且使用的時候,一定要想一想函數內部的實現原理再去使用。切記小心。