1. 程式人生 > >C++之shared_ptr總結

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