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