日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

shared_ptr的一些尴尬

發(fā)布時(shí)間:2023/11/30 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 shared_ptr的一些尴尬 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

http://blog.csdn.net/henan_lujun/article/details/8984543

shared_ptr在boost庫(kù)中已經(jīng)有多年了,C++11又為其正名,把他引入了STL庫(kù),放到了std的下面,可見其頗有用武之地;但是shared_ptr是萬(wàn)能的嗎?有沒(méi)有什么樣的問(wèn)題呢?本文并不說(shuō)明shared_ptr的設(shè)計(jì)原理,也不是為了說(shuō)明如何使用,只說(shuō)一下在使用過(guò)程中的幾點(diǎn)注意事項(xiàng)。

智能指針是萬(wàn)能良藥?

智能指針為解決資源泄漏,編寫異常安全代碼提供了一種解決方案,那么他是萬(wàn)能的良藥嗎?使用智能指針,就不會(huì)再有資源泄漏了嗎?來(lái)看下面的代碼:

[cpp]?view plain?copy
  • //header?file??
  • void?func(?shared_ptr<T1>?ptr1,?shared?ptr<T2>?ptr2?);??
  • ???
  • //call?func?like?this??
  • func(?shared_ptr<T1>(?new?T1()?),?shared_ptr<T2>(?new?T2()?)?);??

  • 上面的函數(shù)調(diào)用,看起來(lái)是安全的,但在現(xiàn)實(shí)世界中,其實(shí)不然:由于C++并未定義一個(gè)表達(dá)式的求值順序,因此上述函數(shù)調(diào)用除了func在最后得到調(diào)用之外是可以確定,其他的執(zhí)行序列則很可能被拆分成如下步驟:

    a.????分配內(nèi)存給T1

    b.???構(gòu)造T1對(duì)象

    c.????分配內(nèi)存給T2

    d.???構(gòu)造T2對(duì)象

    e.????構(gòu)造T1的智能指針對(duì)象

    f.?????構(gòu)造T2的智能指針對(duì)象

    g.???調(diào)用func

    ?

    或者:

    a’. 分配內(nèi)存給T1

    b’. 分配內(nèi)存給T2

    c’. 構(gòu)造T1對(duì)象

    d’. 構(gòu)造T2對(duì)象

    e’. 構(gòu)造T1的智能指針對(duì)象

    f’. 構(gòu)造T2的智能指針對(duì)象

    g’. 調(diào)用func

    上述無(wú)論哪種形式的構(gòu)造序列,如果在c或者d / c’或者d’失敗,則T1對(duì)象所分配內(nèi)存必然泄漏。

    為解決這個(gè)問(wèn)題,有一個(gè)依然使用智能智能的笨重辦法:

    [cpp]?view plain?copy
  • template<class?T>??
  • shared_ptr<T>?shared_ptr_new()??
  • {??
  • ????return?shared_ptr<T>(?new?T?);??
  • }??
  • ???
  • //call?like?this??
  • func(?shared_ptr_new<T1>(),?shared_ptr_new<T2>()?);??
  • 使用這種方法,可以解決因?yàn)楫a(chǎn)生異常導(dǎo)致資源泄漏的問(wèn)題;然而另外一個(gè)問(wèn)題出現(xiàn)了,如果T1或者T2的構(gòu)造函數(shù)需要提供參數(shù)怎么辦呢?難道提供很多個(gè)重載版本?——可以倒是可以,只要你不嫌累,而且有足夠的先見性。

    其實(shí),最最完美的方案,其實(shí)是最簡(jiǎn)單的——就是盡量簡(jiǎn)單的書寫代碼,像這樣:

    [cpp]?view plain?copy
  • //header?file??
  • void?func(?shared_ptr<T1>?ptr1,?shared_ptr<T2>?ptr2?);??
  • ???
  • //call?func?like?this??
  • shared_ptr<T1>?ptr1(?new?T1()?);??
  • shared_ptr<T2>?ptr2(?new?T2()?);??
  • func(ptr1,?ptr2??);??
  • 這樣簡(jiǎn)簡(jiǎn)單單的代碼,避免了異常導(dǎo)致的泄漏。又應(yīng)了那句話:簡(jiǎn)單就是美。其實(shí),在一個(gè)表達(dá)式中,分配多個(gè)資源,或者需要求多個(gè)值等操作都是不安全的。

    歸總一句話:拋棄臨時(shí)對(duì)象,讓所有的智能指針都有名字,就可以避免此類問(wèn)題的發(fā)生。

    ?

    shared_ptr 交叉引用導(dǎo)致的泄漏

    是否讓每個(gè)智能指針都有了名字,就不會(huì)再有內(nèi)存泄漏?不一定。看看下面代碼的輸出,是否感到驚訝?

    [cpp]?view plain?copy
  • class?CLeader;??
  • class?CMember;??
  • ???
  • class?CLeader??
  • {??
  • public:??
  • ??????CLeader()?{?cout?<<?"CLeader::CLeader()"?<<?endl;?}??
  • ??????~CLeader()?{?cout?<<?"CLeader:;~CLeader()?"?<<?endl;?}??
  • ???
  • ??????std::shared_ptr<CMember>?member;??
  • };??
  • ???
  • class?CMember??
  • {??
  • public:??
  • ??????CMember()??{?cout?<<?"CMember::CMember()"?<<?endl;?}??
  • ??????~CMember()?{?cout?<<?"CMember::~CMember()?"?<<?endl;?}??
  • ???
  • ??????std::shared_ptr<CLeader>?leader;?????
  • };??
  • ???
  • void?TestSharedPtrCrossReference()??
  • {??
  • ??????cout?<<?"TestCrossReference<<<"?<<?endl;??
  • ??????boost::shared_ptr<CLeader>?ptrleader(?new?CLeader?);??
  • ??????boost::shared_ptr<CMember>?ptrmember(?new?CMember?);??
  • ???
  • ??????ptrleader->member?=?ptrmember;??
  • ??????ptrmember->leader?=?ptrleader;??
  • ???
  • ??????cout?<<"??ptrleader.use_count:?"?<<?ptrleader.use_count()?<<?endl;??
  • ??????cout?<<"??ptrmember.use_count:?"?<<?ptrmember.use_count()?<<?endl;??
  • }??
  • //output:??
  • CLeader::CLeader()??
  • CMember::CMember()??
  • ??ptrleader.use_count:?2??
  • ??ptrmember.use_count:?2??
  • 從運(yùn)行輸出來(lái)看,兩個(gè)對(duì)象的析構(gòu)函數(shù)都沒(méi)有調(diào)用,也就是出現(xiàn)了內(nèi)存泄漏——原因在于:TestSharedPtrCrossReference()函數(shù)退出時(shí),兩個(gè)shared_ptr對(duì)象的引用計(jì)數(shù)都是2,所以不會(huì)釋放對(duì)象;


    這里出現(xiàn)了常見的交叉引用問(wèn)題,這個(gè)問(wèn)題,即使用原生指針互相記錄時(shí)也需要格外小心;shared_ptr在這里也跌了跟頭,ptrleader和ptrmember在離開作用域的時(shí)候,由于引用計(jì)數(shù)不為1,所以最后一次的release操作(shared_ptr析構(gòu)函數(shù)里面調(diào)用)也無(wú)法destroy掉所托管的資源。

    為了解決這種問(wèn)題,可以采用weak_ptr來(lái)隔斷交叉引用中的回路。所謂的weak_ptr,是一種弱引用,表示只是對(duì)某個(gè)對(duì)象的一個(gè)引用和使用,而不做管理工作;我們把他和shared_ptr來(lái)做一下對(duì)比:

    shared_ptr

    weak_ptr

    強(qiáng)引用

    弱引用

    強(qiáng)引用存在,則引用的對(duì)象必定存在;

    只要有一個(gè)強(qiáng)引用存在,強(qiáng)引用對(duì)象就不能釋放

    是對(duì)象存在時(shí)的一個(gè)引用;

    及時(shí)有弱引用存在,對(duì)象仍然可以釋放

    增加對(duì)象的引用計(jì)數(shù)

    不增加對(duì)象的引用計(jì)數(shù)

    負(fù)責(zé)資源管理,在引用計(jì)數(shù)為0時(shí)釋放資源

    不負(fù)責(zé)資源管理

    有多個(gè)構(gòu)造函數(shù),可以從任意類型初始化

    只能從一個(gè)shared_ptr或者weak_ptr對(duì)象上進(jìn)行初始化

    ?

    行為類似原生指針,不過(guò)可以用expired()判斷對(duì)象是否已經(jīng)釋放

    由于weak_ptr具有上述的一些性質(zhì),所以如果把CMember的聲明改成如下形式,就可以解除這種循環(huán),從而每個(gè)資源都可以順利釋放。

    [cpp]?view plain?copy
  • class?CMember??
  • {??
  • public:??
  • ??????CMember()??{?cout?<<?"CMember::CMember()"?<<?endl;?}??
  • ??????~CMember()?{?cout?<<?"CMember::~CMember()?"?<<?endl;?}??
  • ???
  • ??????boost::weak_ptr<CLeader>?leader;?????
  • };??
  • 這種使用weak_ptr的方式,是基于已暴露問(wèn)題的修正方案,在做設(shè)計(jì)的時(shí)候,一般很難注意到這一點(diǎn);總之,C++缺少垃圾收集機(jī)制,雖然智能指針提供了一個(gè)的解決方案,但他也難以到達(dá)完美;因此,C++中的資源管理必須慎之又慎。

    ?

    類向外傳遞this與shared_ptr

    可以說(shuō),shared_ptr著力解決類對(duì)象一級(jí)的資源管理,至于類對(duì)象內(nèi)部,shared_ptr暫時(shí)還無(wú)法管理;那么這是否會(huì)出現(xiàn)問(wèn)題呢?來(lái)看看這樣的代碼:

    [cpp]?view plain?copy
  • class?Point1??
  • {??
  • public:??
  • ????Point1()?:??X(0),?Y(0)?{?cout?<<?"Point1::Point1(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????Point1(int?x,?int?y)?:??X(x),?Y(y)?{?cout?<<?"Point1::Point1(int?x,?int?y),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????~Point1()?{?cout?<<?"Point1::~Point1(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ???????
  • public:??
  • ????Point1*?Add(const?Point1*?rhs)?{?X?+=?rhs->X;?Y?+=?rhs->Y;?return?this;}??
  • ???
  • private:??
  • ????int?X;??
  • ????int?Y;??
  • };??
  • ???
  • void?TestPoint1Add()??
  • {??
  • ????cout?<<?"TestPoint1Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"?<<?endl;??
  • ????shared_ptr<Point1>?p1(?new?Point1(2,2)?);??
  • ????shared_ptr<Point1>?p2(?new?Point1(3,3)?);??
  • ???????
  • ????p2.reset(?p1->Add(p2.get())?);??
  • }??
  • ???
  • 輸出為:??
  • TestPoint1Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??
  • Point1::Point1(int?x,?int?y),?(2,2)??
  • Point1::Point1(int?x,?int?y),?(3,3)??
  • Point1::~Point1(),?(3,3)??
  • Point1::~Point1(),?(5,5)??
  • Point1::~Point1(),?(5411568,5243076)??
  • 為了使類似Point::Add()::Add()可以連續(xù)進(jìn)行Add操作成為可能,Point1定義了Add方法,并返回了this指針(從Effective C++的條款看,這里最好該以傳值形式返回臨時(shí)變量,在此為了說(shuō)明問(wèn)題,暫且不考慮這種設(shè)計(jì)是否合理,但他就這樣存在了)。在TestPoint1Add()函數(shù)中,使用此返回的指針重置了p2,這樣p2和p1就同時(shí)管理了同一個(gè)對(duì)象,但是他們卻互相不知道這事兒,于是悲劇發(fā)生了。在作用域結(jié)束的時(shí)候,他們兩個(gè)都去對(duì)所管理的資源進(jìn)行析構(gòu),從而出現(xiàn)了上述的輸出。從最后一行輸出也可以看出,所管理的資源,已經(jīng)處于“無(wú)效”的狀態(tài)了。

    ?

    那么,我們是否可以改變一下呢,讓Add返回一個(gè)shared_ptr了呢。我們來(lái)看看Point2:

    [cpp]?view plain?copy
  • class?Point2??
  • {??
  • public:??
  • ????Point2()?:??X(0),?Y(0)?{?cout?<<?"Point2::Point2(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????Point2(int?x,?int?y)?:??X(x),?Y(y)?{?cout?<<?"Point2::Point2(int?x,?int?y),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????~Point2()?{?cout?<<?"Point2::~Point2(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ???????
  • public:??
  • ????shared_ptr<Point2>?Add(const?Point2*?rhs)?{?X?+=?rhs->X;?Y?+=?rhs->Y;?return?shared_ptr<Point2>(this);}??
  • ???
  • private:??
  • ????int?X;??
  • ????int?Y;??
  • };??
  • ???
  • void?TestPoint2Add()??
  • {??
  • ????cout?<<?endl?<<?"TestPoint2Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"?<<?endl;??
  • ????shared_ptr<Point2>?p1(?new?Point2(2,2)?);??
  • ????shared_ptr<Point2>?p2(?new?Point2(3,3)?);??
  • ???????
  • ????p2.swap(?p1->Add(p2.get())?);??
  • }??
  • ???
  • 輸出為:??
  • TestPoint2Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??
  • Point2::Point2(int?x,?int?y),?(2,2)??
  • Point2::Point2(int?x,?int?y),?(3,3)??
  • Point2::~Point2(),?(3,3)??
  • Point2::~Point2(),?(5,5)??
  • Point2::~Point2(),?(3379952,3211460)??

  • 從輸出來(lái)看,哪怕使用shared_ptr來(lái)作為Add函數(shù)的返回值,仍然無(wú)濟(jì)于事;對(duì)象仍然被刪除了兩次;

    ?針對(duì)這種情況,shared_ptr的解決方案是:?enable_shared_from_this這個(gè)模版類。所有需要在內(nèi)部傳遞this指針的類,都從enable_shared_from_this繼承;在需要傳遞this的時(shí)候,使用其成員函數(shù)shared_from_this()來(lái)返回一個(gè)shared_ptr。運(yùn)用這種方案,我們改良我們的Point類,得到如下的Point3:

    [cpp]?view plain?copy
  • class?Point3?:?public?enable_shared_from_this<Point3>??
  • {??
  • public:??
  • ????Point3()?:??X(0),?Y(0)?{?cout?<<?"Point3::Point3(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????Point3(int?x,?int?y)?:??X(x),?Y(y)?{?cout?<<?"Point3::Point3(int?x,?int?y),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ????~Point3()?{?cout?<<?"Point3::~Point3(),?("?<<?X?<<?","?<<?Y?<<?")"?<<?endl;?}??
  • ???????
  • public:??
  • ????shared_ptr<Point3>?Add(const?Point3*?rhs)?{?X?+=?rhs->X;?Y?+=?rhs->Y;?return?shared_from_this();}??
  • ???
  • private:??
  • ????int?X;??
  • ????int?Y;??
  • };??
  • ???
  • void?TestPoint3Add()??
  • {??
  • ????cout?<<?endl?<<?"TestPoint3Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"?<<?endl;??
  • ????shared_ptr<Point3>?p1(?new?Point3(2,2)?);??
  • ????shared_ptr<Point3>?p2(?new?Point3(3,3)?);??
  • ???????
  • ????p2.swap(?p1->Add(p2.get())?);??
  • }??
  • 輸出為:??
  • TestPoint3Add()?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??
  • Point3::Point3(int?x,?int?y),?(2,2)??
  • Point3::Point3(int?x,?int?y),?(3,3)??
  • Point3::~Point3(),?(3,3)??
  • Point3::~Point3(),?(5,5)??
  • 從這個(gè)輸出可以看出,在這里的對(duì)象析構(gòu)已經(jīng)變得正常。因此,在類內(nèi)部需要傳遞this的場(chǎng)景下,enable_shared_from_this是一個(gè)比較靠譜的方案;只不過(guò),要謹(jǐn)慎的記住,使用該方案的一個(gè)前提,就是類的對(duì)象已經(jīng)被shared_ptr管理,否則,就等著拋異常吧。例如:

    [cpp]?view plain?copy
  • Point3?p1(10,?10);??
  • Point3?p2(20,?20);??
  • ???
  • p1.Add(?&p2?);?//此處拋異常??
  • 上面的代碼會(huì)導(dǎo)致crash。那是因?yàn)閜1沒(méi)有被shared_ptr管理。之所以這樣,是由于shared_ptr的構(gòu)造函數(shù)才會(huì)去初始化enable_shared_from_this相關(guān)的引用計(jì)數(shù)(具體可以參考代碼),所以如果對(duì)象沒(méi)有被shared_ptr管理,shared_from_this()函數(shù)就會(huì)出錯(cuò)。

    ?于是,shared_ptr又引入了注意事項(xiàng):

    • 若要在內(nèi)部傳遞this,請(qǐng)考慮從enable_shared_from_this繼承
    • 若從enable_shared_from_this繼承,則類對(duì)象必須讓shared_ptr接管。
    • 如果要使用智能指針,那么就要保持一致,統(tǒng)統(tǒng)使用智能智能,盡量減少raw pointer裸指針的使用。

    ?好嘛,到最后,再做一個(gè)總結(jié):

    • C++沒(méi)有垃圾收集,資源管理需要自己來(lái)做。
    • 智能指針可以部分解決資源管理的工作,但是不是萬(wàn)能的。
    • 使用智能指針的時(shí)候,每個(gè)shared_ptr對(duì)象都應(yīng)該有一個(gè)名字;也就是避免在一個(gè)表達(dá)式內(nèi)做多個(gè)資源的初始化;
    • 避免shared_ptr的交叉引用;使用weak_ptr打破交叉;
    • 使用enable_shared_from_this機(jī)制來(lái)把this從類內(nèi)部傳遞出來(lái);
    • 資源管理保持統(tǒng)一風(fēng)格,要么使用智能指針,要么就全部自己管理裸指針;

    總結(jié)

    以上是生活随笔為你收集整理的shared_ptr的一些尴尬的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。