學習筆記:shared_ptr陷阱

來源:互聯網
上載者:User

標籤:使用   os   io   strong   資料   ar   art   問題   

條款1:不要把一個原生指標給多個shared_ptr管理int* ptr = new int;shared_ptr<int> p1(ptr);shared_ptr<int> p2(ptr); //logic errorptr對象被刪除了2次這種問題比喻成“二龍治水”,在原生指標中也同樣可能發生。 條款2:不要把this指標給shared_ptrclass Test{public:    void Do(){  m_sp =  shared_ptr<Test>(this);  }private:    shared_ptr<Test> m_member_sp;}; Test* t = new Test;shared_ptr<Test> local_sp(t);p->Do(); 發生什麼事呢,t對象被刪除了2次!t對象給了local_sp管理,然後在m_sp =  shared_ptr<Test>(this)這句裡又請了一尊神來管理t。這就發生了條款1裡“二龍治水”錯誤。 條款3:shared_ptr作為被保護的對象的成員時,小心因循環參考造成無法釋放資源。 對象需要相互協作,對象A需要知道對象B的地址,這樣才能給對象B發訊息(或調用其方法)。設計模式中有大量例子,一個對象中有其他對象的指標。現在把原生指標替換為shared_ptr. 假設a對象中含有一個shared_ptr<B>指向b對象;假設b對象中含有一個shared_ptr<A>指向a對象並且a,b對象都是堆中分配的。很輕易就能與他們失去最後聯絡。考慮某個shared_ptr<A> local_a;是我們能最後一個看到a對象的共用智能指標,其use_count==2,因為對象b中持有a的指標。所以當local_a說再見時,local_a只是把a對象的use_count改成1。同理b對象。然後我們再也看不到a,b的影子了,他們就靜靜的躺在堆裡,成為斷線的風箏。 解決方案是:Use weak_ptr to "break cycles."(boost文檔裡寫的)或者顯示的清理 條款4:不要在函數實參裡建立shared_ptr function ( shared_ptr<int>(new int), g( ) );  //有缺陷可能的過程是先new int,然後調g( ),g( )發生異常,shared_ptr<int>沒有建立,int記憶體泄露 shared_ptr<int> p(new int());f(p, g());  //Boost推薦寫法 條款5:對象內部產生shared_ptr 前面說過,不能把this指標直接扔給shared_ptr. 但是沒有禁止在對象內部產生自己的shared_ptr //這是Boost的例子改的。class Y: public boost::enable_shared_from_this<Y>{    boost::shared_ptr<Y> GetSelf()    {        return shared_from_this();    }}; 原理是這樣的。普通的(沒有繼承enable_shared_from_this)類T的shared_ptr<T> p(new T).p作為棧對象佔8個位元組,為了記錄(new T)對象的引用計數,p會在堆上分配16個位元組以儲存引用計數等“智能資訊”。share_ptr沒有“嵌入(intrusive)”到T對象,或者說T對象對share_ptr毫不知 情。Y對象則不同,Y對象已經被“嵌入”了一些share_ptr相關的資訊,目的是為了找到“全域性”的那16位元組的本對象的“智能資訊”。 原理說完了,就是陷阱Y y;boost::shared_ptr<Y> p=  y.GetSelf(); //無知的代碼,y根本就不是new出來的 Y* y = new Y;boost::shared_ptr<Y> p=  y->GetSelf(); //似是而非,仍舊程式崩盤。Boost文檔說,在調用shared_from_this()之前,必須存在一個正常途徑建立的shared_ptr boost::shared_ptr<Y> spy(new Y)boost::shared_ptr<Y> p =  spy->GetSelf(); //OK 條款6 :處理不是new的對象要小心。 int* pi = (int*)malloc(4)shared_ptr<int> sp( pi ) ; //delete馬嘴不對malloc驢頭。 條款7:多線程對引用計數的影響。 如果是輕量級的鎖,比如InterLockIncrement等,對程式影響不大如果是重量級的鎖,就要考慮因為share_ptr維護引用計數而造成的環境切換開銷。1.33版本以後的shared_ptr對引用計數的操作使用的是Lock-Free(類似InterLockIncrement函數族)的操作,應該效率不錯,而且能保證安全執行緒(庫必須保證其安全,程式員都沒有幹預這些隱藏事物的機會)。Boost文檔說read,write同時對shared_ptr操作時,行為不確定。這是因為shared_ptr本身有兩個成員px,pi。多線程同時對px讀寫是要出問題的。與一個int的全域變數多線程讀寫會出問題的原因一樣。 條款8:對象數組用shared_array int* pint = new int[100];shared_array<int> p (pint ); 既然shared_ptr對應著delete;顯然需要一個delete[]對應物shared_array 條款9:學會用刪除器 struct Test_Deleter{       void  operator ()( Test* p){   ::free(p);   }};Test* t = (Test*)malloc(sizeof(Test));new (t) Test; shared_ptr<Test> sp( t ,  Test_Deleter() ); //刪除器可以改變share_ptr銷毀對象行為 有了刪除器,shared_array無用武之地了。template<class T>struct Array_Deleter{       void  operator ()( T*){   delete[] p;   }};int* pint = new int[100];shared_ptr<int> p (pint, Array_Deleter<int>() ); 條款10:學會用分配器 存放引用計數的地方是堆記憶體,需要16-20位元組的開銷。如果大量使用shared_ptr會造成大量記憶體片段。shared_ptr建構函式的第3個參數是分配器,可以解決這個問題。 shared_ptr<Test> p( (new Test), Test_Deleter(), Mallocator<Test>() );注意刪除器Test_Deleter是針對Test類的。分配器是針對shared_ptr內部資料的。 Mallocator<Test>()是個臨時對象(無狀態的),符合STL分配器規約。 template <typename T> class Mallocator {     //略。。。。。。    T * allocate(const size_t n) const {        return singleton_pool<T,sizeof(T)>::malloc();    }    //略。。。。。。 Mallocator傳入Test,實際分配的類型確是class boost::detail::sp_counted_impl_pda<class Test *,                                         struct Test_Deleter,                                         class Mallocator<class Test> >這是用typeid(T).name()列印出來的。可能和rebind相關。 條款11 weak_ptr在使用前需要檢查合法性。weak_ptr<K> wp;{shared_ptr<K>  sp(new K);  //sp.use_count()==1wp = sp; //wp不會改變引用計數,所以sp.use_count()==1shared_ptr<K> sp_ok = wp.lock(); //wp沒有重載->操作符。只能這樣取所指向的對象}shared_ptr<K> sp_null = wp.lock(); //sp_null .use_count()==0;因為上述代碼中sp和sp_ok離開了範圍,其容納的K對象已經被釋放了。得到了一個容納NULL指標的sp_null對象。在使用wp前需要調用wp.expired()函數判斷一下。因為wp還仍舊存在,雖然引用計數等於0,仍有某處“全域”性的儲存塊儲存著這個計數資訊。直到最後一個weak_ptr對象被析構,這塊“堆”儲存塊才能被回收。否則weak_ptr無法直到自己所容納的那個指標資源的目前狀態。 條款12 不要new shared_ptr<T> 本來shared_ptr就是為了管理指標資源的,不要又引入一個需要管理的指標資源shared_ptr<T>* 條款13  盡量不要get class B{...};class D : public B{ ...};  //繼承層次關係 shared_ptr<B> sp (new D);    //通過隱式轉換,儲存D的指標。B* b = sp.get();             //shared_ptr辛辛苦苦隱藏的原生指標就這麼被刨出來了。D* d = dynamic_cast<D*>(b);  //這是使用get的正當理由嗎? 正確的做法shared_ptr<B> spb (new D)  ;shared_ptr<D> spd = shared_dynamic_cast<D>(spb); //變成子類的指標shared_ptr在竭盡全力表演的像一個原生指標,原生指標能乾的事,它也基本上能幹。 另一個同get相關的錯誤shared_ptr<T> sp(new T);shared_ptr<T> sp2( sp.get() ) ;//又一個“二龍治水”執行個體,指標會刪2次而錯誤。 條款14 不要memcpy shared_ptr shared_ptr<B> sp1 (new B)  ;shared_ptr<B> sp2;memcpy(&sp2,&sp1,sizeof(shared_ptr<B>)); //sp2.use_count()==1很顯然,不是通過正常途徑(拷貝構造,賦值運算),引用計數是不會正確增長的。 條款15 使用BOOST預定義的宏去改變shared_ptr行為。 shared_ptr行為由類似BOOST_SP_DISABLE_THREADS這樣的宏控制。需要去學習他們到底是幹什麼的。大師Andrei Alexandrescu設計了一種基於模板策略設計模式的智能指標,通過幾個模板參數去定製化智能指標的行為。Boost卻不以為然,官方解釋是:需要統一的介面,這樣利於大規模書寫。smart_ptr<T,OwnershipPolicy,ConversionPolicy,CheckingPolicy,StoragePolicy> sp(new T);上述介面缺點是外形複雜,看上去像個大花臉。優點是客戶程式員可以輕易的定製行為。 條款17 建構函式裡調用shared_from_this拋例外 class Holder:public enable_shared_from_this<Holder>{public:    Holder() {        shared_ptr<Holder> sp = shared_from_this();        int x = sp.use_count();    }};同前麵條款5,不符合enable_shared_from_this使用前提。 總結:學習了一天就總結出10多條條款,長期研究一下恐怕就出現條款100了。為什麼還要使用shared_ptr呢?有很多開源庫用shared_ptr,而且shared_ptr具有“傳染性”(某網友語:像毒品沾上就甩不掉),拋開它就會有更嚴重的多龍治水現象。shared_ptr作為原生指標的替代品,能解決一定的記憶體泄露問題。實際上初學原生指標時,每個人都遇到過野指標,刪兩次,忘記刪除等問題。學習shared_ptr也會遇到。shared_ptr的確能改善上述問題,並不能完全解決問題。shared_ptr可能在將來佔主流,它最可能號令江湖,否則一大堆auto_ptr,weak_ptr,原生指標,scoped_ptr共存就把人搞糊塗了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.