http://blog.csdn.net/stelalala/article/details/19993425
本文中的shared_ptr以vs2010中的std::tr1::shared_ptr作為研究對象。可能和boost中的有些許差異,特此說明。
基本功能
shared_ptr提供了一個管理內存的簡單有效的方法。shared_ptr能在以下方面給開發提供便利:
1、 使用shared_ptr能有效的解決忘記釋放內存帶來的內存泄漏問題。同時通過自定義刪除器功能還能廣泛的用于任何需要”釋放”的資源管理。
2、 利用weak_ptr和shared_ptr搭配使用能解決一部分由于重復釋放導致的野指針問題。
基本構造方式
不談拷貝構造的話,shared_ptr的基本構造方式有4種:
1、 無參數構造。
2、 傳入一個指針構造一個shared_ptr。例如shared_ptr<Foo> f(new Foo)。
3、 傳入一個指針和一個刪除器構造一個shared_ptr。例子見后文。
4、 傳入一個指針、一個刪除器以及一個allocator構造一個shared_ptr。
當然還有一些其他的,例如從auto_ptr從weak_ptr從null_ptr構造。
另外,類似于void*,shared_ptr<void>可以容納任何類型的指針。
其他常用方法
l use_count()方法。獲取到當前智能指針的引用計數。0表示沒有任何地方引用。
l get()方法。獲取到raw指針。
l reset()方法。重新設置智能指針指向的對象,引用計數重新設置為1。reset方法的函數原型有4種,基本上和前文提到的4種構造函數一一對應。
l swap()方法。交換兩個智能指針的內容。
l operater =()方法。該方法會涉及到引用計數的增加。
關于自定義刪除器
智能指針能夠自定義刪除器是一個很重要的功能,該功能使得能夠跨dll傳遞shared_ptr變為可能(當然前提是多個dll使用的shared_ptr實現要一樣)。尤其是當c++11的Lambda表達式出現后這個功能用起來更加方便。
先來看自定義刪除器的構造方法:
template<class _Ux,
class _Dx>
shared_ptr(_Ux *_Px, _Dx _Dt)
{ // construct with _Px, deleter
_Resetp(_Px, _Dt);
}
其中構造函數的第二個參數就是刪除器。這里要求刪除器:
1、 是”可調用”的即可,例如function object、函數指針、Lambda表達式、bind/functor等等均可。
2、 返回值是void,參數是Ux*
3、 從形參看出,刪除器以傳值的方式傳入,所以要求刪除器要是可拷貝的,否則會編譯出錯。
4、 刪除器不要拋出異常。
例如:
shared_ptr<Foo> shot1(new Foo(1),[&](Foo* p){p->Release();});
make_shared有何用處
boost或者stl都提供了make_shared這個函數。用來方便的創建shared_ptr。
make_shared的好處有兩點:
1、 既然用了shared_ptr不用手動delete指針,那么最好也不要在代碼中出現new。make_shared正是在函數內封裝了new的操作。
2、 從shared_ptr的數據接口了解到,在構造shared_ptr的時候,會new出一個對象保存指針的相關信息。所以一般來說,shared_ptr<Foo> x(new Foo); 需要為Foo 和ref_count 各分配一次內存。如果使用make_shared來創建的話,make_shared內部會盡量將兩次內存分配在連續的位置(這個得看用的什么heap管理)。這里理論上能夠更快一些。
說下缺點:
1、 make_shared只能針對new出來的,對于使用工廠創建出來的對象無能為力。
2、 需要定制刪除器時,make_shared無能為力。
3、 make_shared目前只支持10個參數
另外,make_shared代碼很有意思,為了方便的定義10個參數,宏定義用得鬼斧神工。
如何進行類型cast
如果只能指針聲明為基類的指針,指向的實際類型是子類的話,shared_ptr會自動完成。其他的轉型一眼就能看明白,無需多言:
tr1::const_pointer_cast
tr1::dynamic_pointer_cast
tr1::static_pointer_cast
使用shared_ptr可能會遇到的問題
生命周期的問題
使用shared_ptr的目的就是管理對象的生命周期。在使用了shared_ptr以后有幾個事情會變得和以往不太一樣。
首先,用了shared_ptr就表明對象是使用引用計數來管理,那么該對象什么時候真正被從內存中釋放掉就不是很明顯了。比如說,可能你的代碼中持有了一份shared_ptr的拷貝,就會導致某個對象一直存留下來。
shared_ptr多次引用同一數據
發生這樣的事情后,最好的下場是:后釋放的shared_ptr在析構的時候吐核。
在實際編碼中要注意。不要把一個raw指針交給多個shared_ptr管理。發生這樣的事情很可能是在遺留代碼上使用新特性導致的。
this指針的問題
例如這樣的例子:
class Foo
{
public:
Foo* GetThis()
{
return this;
}
}
要把這樣的代碼改為返回shared_ptr<Foo>,不那么好改。假如直接這樣修改會有嚴重的問題:
shared_ptr<Foo> GetThis()
{
return shared_ptr<Foo>(this);
}
因為shared_ptr<Foo>被使用完后就析構了,引用計數減到0以后就會把this delete掉。照成野指針。
為了解決這個問題,標準庫提供了一個方法:讓類派生自一個模板類:enable_shared_from_this<T>。然后調用shared_from_this()函數即可。
class Foo: public enable_shared_from_this<Foo>
{
public:
shared_ptr<Foo> GetThis()
{
return shared_from_this();;
}
}
這個方法看上去不那么美觀,但是確實解決了一些問題。也帶來了另一些問題:shared_from_this()這個函數不能夠在構造函數中調用。具體原理下一篇文章剖析shared_ptr實現原理時再講吧。
多線程的問題
shared_ptr的線程安全的定義在boost的文檔中有明確的說明:
l 一個shared_ptr對象可以被多個線程同時read
l 兩個shared_ptr對象,指向同一個raw指針,兩個個線程分別write這兩個shared_ptr對象,是安全的。包括析構。
l 多個線程如果要對同一個shared_ptr對象讀寫,是線程不安全的
也就是說,唯一需要注意的就是:多個線程中對同一個shared_ptr對象讀寫時需要加鎖。但是即使是加鎖也有技巧。比較好的方式是:
thread.lock();
shared_ptr tmpPtr=globalSharedPtr; // globalSharedPtr是多個線程讀寫的那個
thread.unlock();
后面的操作均針對tmpPtr進行
…
環形引用的問題
環形引用是指這樣的情況:
Class A的一個實例中持有一個shared_ptr<B>,Class B的一個實例中持有shared_ptr<A>。考慮以下代碼:
class CParent
{
public:
shared_ptr< CChild > children;
};
class CChild
{
public:
shared_ptr< CParent > parent;
};
int main()
{
{
shared_ptr< CParent > pA(new CParent);
shared_ptr< CChild > pB(new CChild);
pA-> children =pB;
pB-> parent =pA;
}
//到這里pA和pB都未能被釋放掉
}
要解決環形引用,沒有特別好的辦法。在分析代碼以后,知道了在某個地方可能有環形引用,那么可以使用weak_ptr來替代shared_ptr。
weak_ptr
weak_ptr本身不具有指針的行為,例如你不能對一個weak_ptr來進行*或者->操作。它通常用來和shared_ptr配合使用。
weak_ptr作為一個”shared_ptr的觀察者”能夠獲知shared_ptr的引用計數,還可以獲知一個shared_ptr是否已經被析構了。單沖這一點來說,就一點不weak了。
構造weak_ptr
有兩種方法可以構造一個weak_ptr
1、 從shared_ptr構造而來。這種情況不會增加shared_ptr的引用計數。當然會增加另一個計數,這個放到下一篇中講。
2、 從另一個weak_ptr拷貝。
也就是說weak_ptr不可能脫離shared_ptr而存在。
expired()
返回布爾,當返回true的時候表示,weak_ptr關聯的shared_ptr已經被析構了。
int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr<foo> fptr=shared_ptr<foo>(new foo(1,2));
weak_ptr<foo> wptr=fptr;
fptr.reset();
if(wptr.expired())
{
cout<<”wptr has expired”<<endl;
}
system(“pause”);
return 0;
}
lock()
從當前的weak_ptr創建一個新的shared_ptr。如果此時expired()返回true時,創建的shared_ptr中將保存一個null_ptr。
use_count()
返回當前關聯的shared_ptr的引用計數是多少。expired()返回true時,該函數返回0。
weak_ptr使用場景
weak_ptr的特性是:weak_ptr不會增加shared_ptr的引用計數,所以weak_ptr通常用來解決shared_ptr無法解決的問題,例如環形引用。weak_ptr常見的使用場景有這么幾個:
1、 想管理某些資源,但是又不想增加引用計數,那么就可以保存weak_ptr。
2、 當知道了有環形引用后,可以使用weak_ptr。例如上面的例子可以改為這樣:
class CParent
{
public:
shared_ptr< CChild > children;
};
class CChild
{
public:
weak_ptr< CParent > parent;
};
int main()
{
{
shared_ptr< CParent > pA(new CParent);
shared_ptr< CChild > pB(new CChild);
pA-> children =pB;
pB-> parent =pA;
}
}
3、 某些情況下,需要知道某個shared_ptr是否已經釋放了。
總結
1、 在遺留代碼上如果要引入shared_ptr要謹慎!shared_ptr帶來的不確定性可能要比帶來的便利性大的多。
2、 使用shared_ptr并不是意味著能偷懶。反而你更需要了解用shared_ptr管理的對象的生命周期應該是什么樣子的,是不是有環形引用,是不是有線程安全問題,是不是會在某個地方意外的被某個東西hold住了。
3、 一個對象如何使用shared_ptr管理那么最好全部使用shared_ptr來管理,必要的時候可以使用weak_ptr。千萬不要raw ptr和智能指針混用
3、 不要以傳遞指針的形式傳遞shared_ptr。
4、 多線程讀寫同一個shared_ptr的時候,可以先加鎖拷貝一份出來,然后解鎖即可。
參考
1、 1、《Boost程序庫完全開發指南》
2、 當析構函數遇到多線程──C++ 中線程安全的對象回調
http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html
3、 為什么多線程讀寫shared_ptr 要加鎖
http://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html
4、 vc stl
————————以上文章轉自:shared_ptr簡介以及常見問題