由於C++語言沒有自動內存回收機制,程序員每次new出來的內存都要手動delete。程序員忘記delete,流程太復雜,最終導致沒有delete,異常導致程序過早退出,沒有執行delete的情況並不罕見。用智能指針便可以有效緩解這類問題,本文主要講解參見的智能指針的用法。包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr。你可能會想,如此多的智能指針就為了解決new、delete匹配問題.
下面就按照順序講解如上7種智能指針(smart_ptr)。
對於編譯器來說,智能指針實際上是一個棧對象,並非指針類型,在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。所有智能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。
訪問智能指針包含的裸指針則可以用get()函數。由於智能指針是一個對象,所以if(my_smart_object)永遠為真,要判斷智能指針的裸指針是否為空,需要這樣判斷:if(my_smart_object.get())。
智能指針包含了reset()方法,如果不傳遞參數(或者傳遞NULL),則智能指針會釋放當前管理的內存。如果傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。
我們編寫一個測試類來輔助分析:
std::auto_ptr屬於STL,當然在namespacestd中,包含頭文件#include
std::auto_ptr的源碼後,我們看到,罪魁禍首是“my_memory=my_memory”,這行代碼,my_memory2完全奪取了my_memory的內存管理所有權,導致my_memory懸空,最後使用時導致崩潰。
所以,使用std::auto_ptr時,絕對不能使用“operator=”操作符。
我們調用release()函數釋放內存,結果卻導致內存洩露(在內存受限系統中,如果my_memory占用太多內存,我們會考慮在使用完成後,立刻歸還,而不是等到my_memory結束生命期後才歸還)。
原來std::auto_ptr的release()函數只是讓出內存所有權,這顯然也不符合C++編程思想。
std::auto_ptr可用來管理單個對象的對內存,但是,請注意如下幾點:
(1)盡量不要使用“operator=”。如果使用了,請不要再使用先前對象。
(2)記住release()函數不會釋放對象,僅僅歸還所有權。
(3)std::auto_ptr最好不要當成參數傳遞(讀者可以自行寫代碼確定為什麼不能)。
(4)由於std::auto_ptr的“operator=”問題,有其管理的對象不能放入std::vector等容器中。
(5)……
使用一個std::auto_ptr的限制還真多,還不能用來管理堆內存數組,這應該是你目前在想的事情吧,我也覺得限制挺多的,哪天一個不小心,就導致問題了。
由於std::auto_ptr引發了諸多問題,一些設計並不是非常符合C++編程思想。
boost::scoped_ptr屬於boost庫,定義在namespaceboost中,包含頭文件#include
boost::scoped_ptr也可以像auto_ptr一樣正常使用。但其沒有release()函數,不會導致先前的內存洩露問題。其次,由於boost::scoped_ptr是獨享所有權的,所以明確拒絕用戶寫“my_memory2=my_memory”之類的語句,可以緩解std::auto_ptr幾個惱人的問題。
由於boost::scoped_ptr獨享所有權,當我們真真需要復制智能指針時,需求便滿足不了,如此我們再引入一個智能指針,專門用於處理復制,參數傳遞的情況,這便是如下的boost::shared_ptr。
boost::shared_ptr
boost::shared_ptr屬於boost庫,定義在namespaceboost中,包含頭文件#include
boost::shared_ptr也可以很方便的使用。並且沒有release()函數。關鍵的一點,boost::shared_ptr內部維護了一個引用計數,由此可以支持復制、參數傳遞等。boost::shared_ptr提供了一個函數use_count(),此函數返回boost::shared_ptr內部的引用計數。當我們需要使用一個共享對象的時候,boost::shared_ptr是再好不過的了。
在多線程中使用shared_ptr時,如果存在拷貝或賦值操作,可能會由於同時訪問引用計數而導致計數無效。解決方法是向每個線程中傳遞公共的week_ptr,線程中需要使用shared_ptr時,將week_ptr轉換成shared_ptr即可。你可以用下列方法把shared_ptr傳遞給另一個函數:
向shared_ptr傳遞值。調用復制構造函數,遞增引用計數,並把被調用方當做所有者。還有就是在這次操作中有少量的開銷,這很大程度上取決於你傳遞了多少shared_ptr對象。當調用方和被調用方之間的代碼協定(隱式或顯式)要求被調用方是所有者,使用此選項。
通過引用或常量引用來傳遞shared_ptr。在這種情況下,引用計數不增加,並且只要調用方不超出范圍,被調用方就可以訪問指針。或者,被調用方可以決定創建一個基於引用的shared_ptr,從而成為一個共享所有者。當調用者並不知道被被調用方,或當您必須傳遞一個shared_ptr,並希望避免由於性能原因的復制操作,請使用此選項。
通過底層的指針或引用底層的對象。這使得被調用方使用對象,但不使共享所有權或擴展生存期。如果被調用方從原始指針創建一個shared_ptr,則新的shared_ptr是獨立於原來的,且沒有控制底層的資源。當調用方和被調用方之間的協定中明確規定調用者保留shared_ptr生存期的所有權,則使用此選項。
當您決定如何傳遞一個shared_ptr時,確定被調用方是否有共享基礎資源的所有權。一個“所有者”就是只要它需要就可以使用底層資源的對象或函數。如果調用方必須保證被調用方可以在其(函數)生存期以外擴展指針的生存期,請使用第一個選項。如果您不關心被調用方是否擴展生存期,則通過引用傳遞並讓被調用方復制它。
如果不得不允許幫助程序函數訪問底層指針,並且您知道幫助程序函數將使用指針且在調用函數返回前先返回,則該函數不必共享底層指針的所有權。僅僅是在調用方的shared_ptr的生存期內允許訪問指針。在這種情況下,通過引用來傳遞shared_ptr,通過原始指針或引用的基本對象都是安全的。通過此方式提供一個小的性能改進,並且還有助於表示程序的意圖。
有時,例如在一個std:vector
boost::scoped_array
boost::scoped_array屬於boost庫,定義在namespaceboost中,包含頭文件#include
boost::scoped_array便是用於管理動態數組的。跟boost::scoped_ptr一樣,也是獨享所有權的。
boost::scoped_array
一個用引用計數解決復制、參數傳遞的智能指針類。
boost::shared_array屬於boost庫,定義在namespaceboost中,包含頭文件#include
由於boost::scoped_array獨享所有權,顯然在很多情況下(參數傳遞、對象賦值等)不滿足需求,由此我們引入boost::shared_array。跟boost::shared_ptr一樣,內部使用了引用計數。
跟boost::shared_ptr一樣,使用了引用計數,可以復制,通過參數來傳遞。
至此,我們講過的智能指針有std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。這幾個智能指針已經基本夠我們使用了,90%的使用過標准智能指針的代碼就這5種。可如下還有兩種智能指針,它們肯定有用,但有什麼用處呢,一起看看吧。
boost::weak_ptr
boost::weak_ptr屬於boost庫,定義在namespaceboost中,包含頭文件#include
在講boost::weak_ptr之前,讓我們先回顧一下前面講解的內容。似乎boost::scoped_ptr、boost::shared_ptr這兩個智能指針就可以解決所有單個對象內存的管理了,這兒還多出一個boost::weak_ptr,是否還有某些情況我們沒納入考慮呢?
回答:有。首先boost::weak_ptr是專門為boost::shared_ptr而准備的。有時候,我們只關心能否使用對象,並不關心內部的引用計數。boost::weak_ptr是boost::shared_ptr的觀察者(Observer)對象,觀察者意味著boost::weak_ptr只對boost::shared_ptr進行引用,而不改變其引用計數,當被觀察的boost::shared_ptr失效後,相應的boost::weak_ptr也相應失效。
boost::weak_ptr
boost::shared_ptr
我們看到,盡管被賦值了,內部的引用計數並沒有什麼變化。現在要說的問題是,boost::weak_ptr到底有什麼作用呢?從上面那個例子看來,似乎沒有任何作用,其實boost::weak_ptr主要用在軟件架構設計中,可以在基類(此處的基類並非抽象基類,而是指繼承於抽象基類的虛基類)中定義一個boost::weak_ptr,用於指向子類的boost::shared_ptr,這樣基類僅僅觀察自己的boost::weak_ptr是否為空就知道子類有沒對自己賦值了,而不用影響子類boost::shared_ptr的引用計數,用以降低復雜度,更好的管理對象。
boost::intrusive_ptr屬於boost庫,定義在namespaceboost中,包含頭文件#include
講完如上6種智能指針後,對於一般程序來說C++堆內存管理就夠用了,現在有多了一種boost::intrusive_ptr,這是一種插入式的智能指針,內部不含有引用計數,需要程序員自己加入引用計數,不然編譯不過。個人感覺這個智能指針沒太大用處,至少我沒用過。有興趣的朋友自己研究一下源代碼哦。
自己動手的一個帶引用計數的智能指針: #includeusing namespace std; int const MEM_ALLOC = 100; class HeapTable { public: HeapTable() { mhead = new Node; } void AddRef(void *ptr) { Node *p = mhead->mpnext; while (p!=NULL) { if(p->mheapaddr == ptr) { p->counter++; return; } p = p->mpnext; } p = new Node(ptr); p->mpnext = mhead->mpnext; mhead->mpnext = p; } void DelRef(void *ptr) { Node *p = mhead->mpnext; while (p!=NULL) { if(p->mheapaddr == ptr) { p->counter--; return; } p = p->mpnext; } } int GetRef(void *ptr) { Node *p = mhead->mpnext; while (p!=NULL) { if(p->mheapaddr == ptr) { return p->counter; } p = p->mpnext; } return -1; } private: class Node { public: Node(void *ptr = NULL):mheapaddr(ptr),counter(0),mpnext(NULL) { if(mheapaddr != NULL)//有一個新的節點 { counter = 1; } } static void *operator new(size_t size); static void operator delete(void *ptr); void *mheapaddr; int counter; Node *mpnext; static Node*mFreeList; }; Node *mhead; }; HeapTable::Node* HeapTable::Node::mFreeList = NULL; void *HeapTable::Node::operator new(size_t size) { Node *p = NULL; if(mFreeList == NULL) { int allocsize = size*MEM_ALLOC; mFreeList = (Node*)new char[allocsize]; for(p = mFreeList;p mpnext = p+1; } p->mpnext = NULL; } p = mFreeList; mFreeList=mFreeList->mpnext; //從頭取結點 return p; } void HeapTable::Node::operator delete(void *ptr) { if(ptr == NULL) { return ; } Node *p = (Node*)ptr; p->mpnext = mFreeList; mFreeList = p; } template class CSmartPtr { public: CSmartPtr(T *ptr = NULL); ~CSmartPtr(); CSmartPtr(const CSmartPtr &src); CSmartPtr & operator=(const CSmartPtr &src); T& operator*(){return *mptr;} void AddRef(); void DelRef(); int GetRef(); const T&operator*()const{return *mptr;} const T *operator->()const{return mptr;} private: T *mptr; static HeapTable mHeapTable; }; template HeapTable CSmartPtr ::mHeapTable; template CSmartPtr ::CSmartPtr(T *ptr = NULL) { mptr = ptr; if(mptr != NULL) { AddRef(); } } template CSmartPtr ::CSmartPtr(const CSmartPtr &src) { mptr = src.mptr; if(mptr!= NULL) { AddRef(); } } template CSmartPtr & CSmartPtr ::operator= (const CSmartPtr &src) { if(this == &src) { return *this; } DelRef();//減去引用計數 if(GetRef() == 0) { delete mptr; mptr = NULL; } AddRef(); mptr = src.mptr; return *this; } template CSmartPtr ::~CSmartPtr() { DelRef(); if(GetRef() == 0) { delete mptr; } } template void CSmartPtr ::AddRef() { mHeapTable.AddRef(mptr); } template void CSmartPtr ::DelRef() { mHeapTable.DelRef(mptr); } template int CSmartPtr ::GetRef() { return mHeapTable.GetRef(mptr); } class Text { public: Text(){cout<<"construction call!"< mptr(new Text); CSmartPtr mptr2; mptr2 = mptr; CSmartPtr mptr1(new CHello); return 0; }
如上講了這麼多智能指針,有必要對這些智能指針做個總結:
1、在可以使用boost庫的場合下,拒絕使用std::auto_ptr,因為其不僅不符合C++編程思想,而且極容易出錯[2]。
2、在確定對象無需共享的情況下,使用boost::scoped_ptr(當然動態數組使用boost::scoped_array)。
3、在對象需要共享的情況下,使用boost::shared_ptr(當然動態數組使用boost::shared_array)。
4、在需要訪問boost::shared_ptr對象,而又不想改變其引用計數的情況下,使用boost::weak_ptr,一般常用於軟件框架設計中。
5、最後一點,也是要求最苛刻一點:在你的代碼中,不要出現delete關鍵字(或C語言的free函數),因為可以用智能指針去管理。