一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

shared_ptr的一些尷尬

 oskycar 2018-10-10

shared_ptr在boost庫中已經有多年了,C++11又為其正名,把他引入了STL庫,放到了std的下面,可見其頗有用武之地;但是shared_ptr是萬能的嗎?有沒有什么樣的問題呢?本文并不說明shared_ptr的設計原理,也不是為了說明如何使用,只說一下在使用過程中的幾點注意事項。

智能指針是萬能良藥?

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

  1. //header file
  2. void func( shared_ptr<T1> ptr1, shared ptr<T2> ptr2 );
  3. //call func like this
  4. func( shared_ptr<T1>( new T1() ), shared_ptr<T2>( new T2() ) );

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

a.    分配內存給T1

b.   構造T1對象

c.    分配內存給T2

d.   構造T2對象

e.    構造T1的智能指針對象

f.     構造T2的智能指針對象

g.   調用func

 

或者:

a’. 分配內存給T1

b’. 分配內存給T2

c’. 構造T1對象

d’. 構造T2對象

e’. 構造T1的智能指針對象

f’. 構造T2的智能指針對象

g’. 調用func

上述無論哪種形式的構造序列,如果在c或者d / c’或者d’失敗,則T1對象所分配內存必然泄漏。

為解決這個問題,有一個依然使用智能智能的笨重辦法:

  1. template<class T>
  2. shared_ptr<T> shared_ptr_new()
  3. {
  4. return shared_ptr<T>( new T );
  5. }
  6. //call like this
  7. func( shared_ptr_new<T1>(), shared_ptr_new<T2>() );

使用這種方法,可以解決因為產生異常導致資源泄漏的問題;然而另外一個問題出現了,如果T1或者T2的構造函數需要提供參數怎么辦呢?難道提供很多個重載版本?——可以倒是可以,只要你不嫌累,而且有足夠的先見性。

其實,最最完美的方案,其實是最簡單的——就是盡量簡單的書寫代碼,像這樣:

  1. //header file
  2. void func( shared_ptr<T1> ptr1, shared_ptr<T2> ptr2 );
  3. //call func like this
  4. shared_ptr<T1> ptr1( new T1() );
  5. shared_ptr<T2> ptr2( new T2() );
  6. func(ptr1, ptr2 );

這樣簡簡單單的代碼,避免了異常導致的泄漏。又應了那句話:簡單就是美。其實,在一個表達式中,分配多個資源,或者需要求多個值等操作都是不安全的。

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

 

shared_ptr 交叉引用導致的泄漏

是否讓每個智能指針都有了名字,就不會再有內存泄漏?不一定??纯聪旅娲a的輸出,是否感到驚訝?

  1. class CLeader;
  2. class CMember;
  3. class CLeader
  4. {
  5. public:
  6. CLeader() { cout << "CLeader::CLeader()" << endl; }
  7. ~CLeader() { cout << "CLeader:;~CLeader() " << endl; }
  8. std::shared_ptr<CMember> member;
  9. };
  10. class CMember
  11. {
  12. public:
  13. CMember() { cout << "CMember::CMember()" << endl; }
  14. ~CMember() { cout << "CMember::~CMember() " << endl; }
  15. std::shared_ptr<CLeader> leader;
  16. };
  17. void TestSharedPtrCrossReference()
  18. {
  19. cout << "TestCrossReference<<<" << endl;
  20. boost::shared_ptr<CLeader> ptrleader( new CLeader );
  21. boost::shared_ptr<CMember> ptrmember( new CMember );
  22. ptrleader->member = ptrmember;
  23. ptrmember->leader = ptrleader;
  24. cout <<" ptrleader.use_count: " << ptrleader.use_count() << endl;
  25. cout <<" ptrmember.use_count: " << ptrmember.use_count() << endl;
  26. }
  27. //output:
  28. CLeader::CLeader()
  29. CMember::CMember()
  30. ptrleader.use_count: 2
  31. ptrmember.use_count: 2

從運行輸出來看,兩個對象的析構函數都沒有調用,也就是出現了內存泄漏——原因在于:TestSharedPtrCrossReference()函數退出時,兩個shared_ptr對象的引用計數都是2,所以不會釋放對象;


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

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

shared_ptr

weak_ptr

強引用

弱引用

強引用存在,則引用的對象必定存在;

只要有一個強引用存在,強引用對象就不能釋放

是對象存在時的一個引用;

及時有弱引用存在,對象仍然可以釋放

增加對象的引用計數

不增加對象的引用計數

負責資源管理,在引用計數為0時釋放資源

不負責資源管理

有多個構造函數,可以從任意類型初始化

只能從一個shared_ptr或者weak_ptr對象上進行初始化

 

行為類似原生指針,不過可以用expired()判斷對象是否已經釋放

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

  1. class CMember
  2. {
  3. public:
  4. CMember() { cout << "CMember::CMember()" << endl; }
  5. ~CMember() { cout << "CMember::~CMember() " << endl; }
  6. boost::weak_ptr<CLeader> leader;
  7. };

這種使用weak_ptr的方式,是基于已暴露問題的修正方案,在做設計的時候,一般很難注意到這一點;總之,C++缺少垃圾收集機制,雖然智能指針提供了一個的解決方案,但他也難以到達完美;因此,C++中的資源管理必須慎之又慎。

 

類向外傳遞this與shared_ptr

可以說,shared_ptr著力解決類對象一級的資源管理,至于類對象內部,shared_ptr暫時還無法管理;那么這是否會出現問題呢?來看看這樣的代碼:

  1. class Point1
  2. {
  3. public:
  4. Point1() : X(0), Y(0) { cout << "Point1::Point1(), (" << X << "," << Y << ")" << endl; }
  5. Point1(int x, int y) : X(x), Y(y) { cout << "Point1::Point1(int x, int y), (" << X << "," << Y << ")" << endl; }
  6. ~Point1() { cout << "Point1::~Point1(), (" << X << "," << Y << ")" << endl; }
  7. public:
  8. Point1* Add(const Point1* rhs) { X += rhs->X; Y += rhs->Y; return this;}
  9. private:
  10. int X;
  11. int Y;
  12. };
  13. void TestPoint1Add()
  14. {
  15. cout << "TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;
  16. shared_ptr<Point1> p1( new Point1(2,2) );
  17. shared_ptr<Point1> p2( new Point1(3,3) );
  18. p2.reset( p1->Add(p2.get()) );
  19. }
  20. 輸出為:
  21. TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  22. Point1::Point1(int x, int y), (2,2)
  23. Point1::Point1(int x, int y), (3,3)
  24. Point1::~Point1(), (3,3)
  25. Point1::~Point1(), (5,5)
  26. Point1::~Point1(), (5411568,5243076)

為了使類似Point::Add()::Add()可以連續(xù)進行Add操作成為可能,Point1定義了Add方法,并返回了this指針(從Effective C++的條款看,這里最好該以傳值形式返回臨時變量,在此為了說明問題,暫且不考慮這種設計是否合理,但他就這樣存在了)。在TestPoint1Add()函數中,使用此返回的指針重置了p2,這樣p2和p1就同時管理了同一個對象,但是他們卻互相不知道這事兒,于是悲劇發(fā)生了。在作用域結束的時候,他們兩個都去對所管理的資源進行析構,從而出現了上述的輸出。從最后一行輸出也可以看出,所管理的資源,已經處于“無效”的狀態(tài)了。

 

那么,我們是否可以改變一下呢,讓Add返回一個shared_ptr了呢。我們來看看Point2:

  1. class Point2
  2. {
  3. public:
  4. Point2() : X(0), Y(0) { cout << "Point2::Point2(), (" << X << "," << Y << ")" << endl; }
  5. Point2(int x, int y) : X(x), Y(y) { cout << "Point2::Point2(int x, int y), (" << X << "," << Y << ")" << endl; }
  6. ~Point2() { cout << "Point2::~Point2(), (" << X << "," << Y << ")" << endl; }
  7. public:
  8. shared_ptr<Point2> Add(const Point2* rhs) { X += rhs->X; Y += rhs->Y; return shared_ptr<Point2>(this);}
  9. private:
  10. int X;
  11. int Y;
  12. };
  13. void TestPoint2Add()
  14. {
  15. cout << endl << "TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;
  16. shared_ptr<Point2> p1( new Point2(2,2) );
  17. shared_ptr<Point2> p2( new Point2(3,3) );
  18. p2.swap( p1->Add(p2.get()) );
  19. }
  20. 輸出為:
  21. TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  22. Point2::Point2(int x, int y), (2,2)
  23. Point2::Point2(int x, int y), (3,3)
  24. Point2::~Point2(), (3,3)
  25. Point2::~Point2(), (5,5)
  26. Point2::~Point2(), (3379952,3211460)

從輸出來看,哪怕使用shared_ptr來作為Add函數的返回值,仍然無濟于事;對象仍然被刪除了兩次;

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

  1. class Point3 : public enable_shared_from_this<Point3>
  2. {
  3. public:
  4. Point3() : X(0), Y(0) { cout << "Point3::Point3(), (" << X << "," << Y << ")" << endl; }
  5. Point3(int x, int y) : X(x), Y(y) { cout << "Point3::Point3(int x, int y), (" << X << "," << Y << ")" << endl; }
  6. ~Point3() { cout << "Point3::~Point3(), (" << X << "," << Y << ")" << endl; }
  7. public:
  8. shared_ptr<Point3> Add(const Point3* rhs) { X += rhs->X; Y += rhs->Y; return shared_from_this();}
  9. private:
  10. int X;
  11. int Y;
  12. };
  13. void TestPoint3Add()
  14. {
  15. cout << endl << "TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;
  16. shared_ptr<Point3> p1( new Point3(2,2) );
  17. shared_ptr<Point3> p2( new Point3(3,3) );
  18. p2.swap( p1->Add(p2.get()) );
  19. }
  20. 輸出為:
  21. TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  22. Point3::Point3(int x, int y), (2,2)
  23. Point3::Point3(int x, int y), (3,3)
  24. Point3::~Point3(), (3,3)
  25. Point3::~Point3(), (5,5)

從這個輸出可以看出,在這里的對象析構已經變得正常。因此,在類內部需要傳遞this的場景下,enable_shared_from_this是一個比較靠譜的方案;只不過,要謹慎的記住,使用該方案的一個前提,就是類的對象已經被shared_ptr管理,否則,就等著拋異常吧。例如:

  1. Point3 p1(10, 10);
  2. Point3 p2(20, 20);
  3. p1.Add( &p2 ); //此處拋異常
上面的代碼會導致crash。那是因為p1沒有被shared_ptr管理。之所以這樣,是由于shared_ptr的構造函數才會去初始化enable_shared_from_this相關的引用計數(具體可以參考代碼),所以如果對象沒有被shared_ptr管理,shared_from_this()函數就會出錯。

 于是,shared_ptr又引入了注意事項:

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

 好嘛,到最后,再做一個總結:

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

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    五月情婷婷综合激情综合狠狠| 大尺度剧情国产在线视频| 欧美不卡高清一区二区三区| 日本免费一本一二区三区| 中文字幕五月婷婷免费| 狠色婷婷久久一区二区三区| 91亚洲人人在字幕国产| 99久久免费中文字幕| 国产成人精品国产成人亚洲 | 亚洲女同一区二区另类| 激情内射日本一区二区三区| 男女激情视频在线免费观看| 国产又粗又硬又大又爽的视频| 欧美一级日韩中文字幕| 日本人妻的诱惑在线观看| 亚洲伊人久久精品国产| 黑丝袜美女老师的小逼逼| 中文字幕日韩无套内射| 精品人妻一区二区三区四在线 | 亚洲专区中文字幕视频| 国产精品第一香蕉视频| 在线观看视频成人午夜| 日本在线高清精品人妻| 俄罗斯胖女人性生活视频| 欧美美女视频在线免费看| 亚洲国产成人精品福利| 熟女体下毛荫荫黑森林自拍| 91精品视频免费播放| 日本欧美一区二区三区就| 人妻露脸一区二区三区| 久久re6热在线视频| 91精品国产综合久久精品| 国内九一激情白浆发布| 欧美日韩综合在线精品| 国产三级视频不卡在线观看| 丰满熟女少妇一区二区三区| 老司机精品线观看86| 日韩美成人免费在线视频| 亚洲精选91福利在线观看| 亚洲精品伦理熟女国产一区二区 | 精品丝袜一区二区三区性色|