引用計數智能指針
<a>C++ <span style="font-family:宋體;">智能指針具體解釋</span></a>
一、簡單介紹
因為 C++ 語言沒有自己主動內存回收機制。程序猿每次 new 出來的內存都要手動 delete。
程序猿忘記 delete。流程太復雜。終於導致沒有 delete。異常導致程序過早退出,沒有運行 delete 的情況並不罕見。
用智能指針便能夠有效緩解這類問題,本文主要解說參見的智能指針的使用方法。包含:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr
以下就依照順序解說如上 7 種智能指針(smart_ptr)。
二、詳細使用
對於編譯器來說,智能指針實際上是一個棧對象。並不是指針類型。在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。全部智能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。
訪問智能指針包括的裸指針則能夠用 get()
因為智能指針是一個對象,所以
if (my_smart_object)永遠為真,要推斷智能指針的裸指針是否為空,須要這樣推斷:if (my_smart_object.get())。智能指針包括了 reset() 方法,假設不傳遞參數(或者傳遞 NULL),則智能指針會釋放當前管理的內存。
假設傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。
我們編寫一個測試類來輔助分析:
std::auto_ptr
std::auto_ptr 屬於 STL。當然在 namespace std 中,包括頭文件 #include<memory> 便可以使用。std::auto_ptr
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::scoped_ptr 屬於boost 庫。定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。
boost::scoped_ptr 跟 std::auto_ptr 一樣,能夠方便的管理單個堆內存對象,特別的是。boost::scoped_ptr 獨享全部權,避免了 std::auto_ptr 惱人的幾個問題。
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 庫。定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。在上面我們看到 boost::scoped_ptr 獨享全部權。不同意賦值、拷貝。boost::shared_ptr 是專門用於共享全部權的。因為要共享全部權。其在內部使用了引用計數。boost::shared_ptr 也是用於管理單個堆內存對象的。
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<shared_ptr<T>>中,您可能必須對傳遞每一個shared_ptr 給lambda表達式體或命名函數對象。 假設lambda或函數沒有存儲指針,則通過引用傳遞shared_ptr。以避免調用拷貝構造函數的每一個元素。
boost::scoped_array
boost::scoped_array 屬於 boost 庫,定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。
boost::scoped_array 便是用於管理動態數組的。跟 boost::scoped_ptr 一樣,也是獨享全部權的。
boost::scoped_array<Simple> my_memory(new Simple[2]); // 使用內存數組來初始化boost::scoped_array 的使用跟 boost::scoped_ptr 差點兒相同。不支持復制,而且初始化的時候須要使用動態數組。另外。boost::scoped_array 沒有重載“operator*”,事實上這並無大礙,普通情況下,我們使用 get() 函數更明白些了。
boost::shared_array
一個用引用計數解決復制、參數傳遞的智能指針類。
boost::shared_array 屬於 boost 庫,定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。
因為 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 庫,定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。
在講 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<Simple> my_memory_weak;
boost::shared_ptr<Simple> my_memory(new Simple(1))
我們看到,雖然被賦值了。內部的引用計數並沒有什麽變化。如今要說的問題是,boost::weak_ptr 究竟有什麽作用呢?從上面那個樣例看來,似乎沒有不論什麽作用,事實上 boost::weak_ptr 主要用在軟件架構設計中,能夠在基類(此處的基類並不是抽象基類,而是指繼承於抽象基類的虛基類)中定義一個 boost::weak_ptr。用於指向子類的 boost::shared_ptr,這樣基類只觀察自己的 boost::weak_ptr 是否為空就知道子類有沒對自己賦值了。而不用影響子類 boost::shared_ptr 的引用計數,用以減少復雜度,更好的管理對象。
boost::intrusive_ptr
boost::intrusive_ptr屬於 boost 庫,定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。
講完如上 6 種智能指針後。對於一般程序來說 C++ 堆內存管理就夠用了,如今有多了一種 boost::intrusive_ptr,這是一種插入式的智能指針,內部不含有引用計數。須要程序猿自己增加引用計數。不然編譯只是。個人感覺這個智能指針沒太大用處,至少我沒用過。有興趣的朋友自己研究一下源碼哦。
自己動手的一個帶引用計數的智能指針: #include <iostream> using 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<mFreeList+MEM_ALLOC-1;++p)//靜態鏈表 { 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<typename T> class CSmartPtr { public: CSmartPtr(T *ptr = NULL); ~CSmartPtr(); CSmartPtr(const CSmartPtr<T> &src); CSmartPtr<T>& operator=(const CSmartPtr<T> &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<typename T> HeapTable CSmartPtr<T>::mHeapTable; template<typename T> CSmartPtr<T>::CSmartPtr(T *ptr = NULL) { mptr = ptr; if(mptr != NULL) { AddRef(); } } template<typename T> CSmartPtr<T>::CSmartPtr(const CSmartPtr<T> &src) { mptr = src.mptr; if(mptr!= NULL) { AddRef(); } } template<typename T> CSmartPtr<T>& CSmartPtr<T>::operator= (const CSmartPtr<T> &src) { if(this == &src) { return *this; } DelRef();//減去引用計數 if(GetRef() == 0) { delete mptr; mptr = NULL; } AddRef(); mptr = src.mptr; return *this; } template<typename T> CSmartPtr<T>::~CSmartPtr() { DelRef(); if(GetRef() == 0) { delete mptr; } } template<typename T> void CSmartPtr<T>::AddRef() { mHeapTable.AddRef(mptr); } template<typename T> void CSmartPtr<T>::DelRef() { mHeapTable.DelRef(mptr); } template <typename T> int CSmartPtr<T>::GetRef() { return mHeapTable.GetRef(mptr); } class Text { public: Text(){cout<<"construction call!"<<endl;} ~Text(){cout<<"destruction call !"<<endl;} private: int ma; int mb; }; class CHello { public: CHello(){cout<<"construction call! hello "<<endl;} ~CHello(){cout<<"destruction call! byebye"<<endl;} private: int ma; int mb; }; int main () { CSmartPtr<Text> mptr(new Text); CSmartPtr<Text> mptr2; mptr2 = mptr; CSmartPtr<CHello> mptr1(new CHello); return 0; }
總結
如上講了這麽多智能指針,有必要對這些智能指針做個總結:
1、在能夠使用 boost 庫的場合下,拒絕使用 std::auto_ptr,由於其不僅不符合 C++ 編程思想。並且極easy出錯[2]。
2、在確定對象無需共享的情況下。使用 boost::scoped_ptr(當然動態數組使用 boost::scoped_array)。
3、在對象須要共享的情況下,使用 boost::shared_ptr(當然動態數組使用 boost::shared_array)。
4、在須要訪問 boost::shared_ptr 對象。而又不想改變其引用計數的情況下,使用 boost::weak_ptr,一般經常使用於軟件框架設計中。
5、最後一點,也是要求最苛刻一點:在你的代碼中,不要出現 delete keyword(或 C 語言的 free 函數)。由於能夠用智能指針去管理。
引用計數智能指針