引言:
ios
因爲 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete。程序員忘記 delete,流程太複雜,最終致使沒有 delete,異常致使程序過早退出,沒有執行 delete 的狀況並不罕見。程序員
RAII(Resource Acquisition Is Initialization)安全
資源分配即初始化,定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,能夠保證資源的正確初始化和釋放。數據結構
所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。ide
STL--auto_ptr函數
Boost庫的智能指針(ps:新的C++11標準中已經引入了unique_ptr/shared_ptr/weak_ptr)ui
在這裏,對於aut_optr,scoped_ptr , shared_ptr 進行剖析this
一. aut_optrspa
std::auto_ptr 屬於 STL,固然在 namespace std 中,包含頭文件 #include<memory> 即可以使用。std::auto_ptr 可以方便的管理單個堆內存對象。爲了解決單個對象被重複釋放屢次的狀況,咱們使用的aut_optr的目的就是轉移權限,顧名思義就是說再拷貝構造新的對象時,因爲此時有兩個對象指向同一塊空間,因此將原來的指針賦NULL,而後將這塊空間的全部權交給新的對象指針。線程
對應代碼:<AutoPtr>
#include<iostream> using namespace std; template<class T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) {} AutoPtr() :_ptr(NULL) {} AutoPtr<T>(AutoPtr<T>& ap) //權限轉移 : _ptr(ap._ptr) { ap._ptr = NULL; } AutoPtr<T>& operator=(AutoPtr<T>& ap) { if (&ap != this) { delete _ptr; _ptr = ap._ptr; ap._ptr = NULL; //權限轉移 } return *this; } ~AutoPtr() { if (_ptr) { delete _ptr; _ptr = NULL; } } T& operator*() { return *_ptr; } private: T* _ptr; }; void Test() { AutoPtr<int> ap1(new int(2)); AutoPtr<int> ap2 = ap1; AutoPtr<int> ap3(new int(3)); ap3 = ap1; } int main() { Test(); return 0; }
雖然表面上解決了重複釋放的問題,可是卻存在一個很大問題,請看右圖:
(先忽略 s 的存在)
如有s2對象,指向本身的一塊空間,如今要拷貝構造一個s3對象,那麼順應上面的代碼,s2會被置NULL,而這塊空間重歸s3所管理。
若是考慮到這塊空間已經有多個指針指向,即s和s2,那麼再拷貝構造s3時,s2被置空,此時,s變成垂懸指針。這就是一個打的問題所在!
二. scoped_ptr
實用的智能指針,思想就是防拷貝,在大多時候用不到拷貝構造和賦值運算符重載,那麼咱們作的就是寫出構造函數和析構函數,拷貝構造和賦值運算符重載只聲明不定義。這裏有幾點要說明:
<1>在代碼中,將拷貝構造和賦值運算符重載設置成保護或者私有的,爲何?世界之大,總有些人想害朕。。話歸主題,緣由是防止有壞人在外面修改,假若被修改也不會調用修改的,由於沒法訪問獲得。
<2>爲何要聲明拷貝構造和賦值函數,緣由是若是不聲明,編譯器會調用本身默認的構造和賦值函數,那麼防拷貝就無從談起。
代碼以下:<ScopedPtr>
#include<iostream> using namespace std; template<class T> class ScopedPtr { public: ScopedPtr(T* ptr) :_ptr(ptr) {} Scoped() :_ptr(NULL) {} ~ScopedPtr() { if (_ptr) { delete _ptr; _ptr = NULL; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetPtr() { return _ptr; } protected: ScopedPtr<T>(const ScopedPtr<T>& sp);// 只聲明不定義,防拷貝 ScopedPtr<T>& operator = (const ScopedPtr<T>& sp); private: T* _ptr; }; void Test() { ScopedPtr<int> sp1(new int(2)); ScopedPtr<int> sp2 = sp1; ScopedPtr<int> sp3(new int(3)); sp3 = sp1; } int main() { Test(); return 0; }
auto_ptr和scopedptr的取捨:
boost::scoped_ptr 用於確保動態分配的對象可以被正確地刪除。scoped_ptr 有着與std::auto_ptr相似的特性,而最大的區別在於它不能轉讓全部權而auto_ptr能夠。事實上,scoped_ptr永遠不能被複制或被賦值!scoped_ptr 擁有它所指向的資源的全部權,並永遠不會放棄這個全部權。scoped_ptr的這種特性提高了咱們的代碼的表現,咱們能夠根據須要選擇最合適的智能指針(scoped_ptr 或 auto_ptr)。要決定使用std::auto_ptr仍是boost::scoped_ptr, 就要考慮轉移全部權是否是你想要的智能指針的一個特性。若是不是,就用scoped_ptr. 它是一種輕量級的智能指針;使用它不會使你的程序變大或變慢。它只會讓你的代碼更安全,更好維護。
三. sharede_ptr
在上面咱們看到 boost::scoped_ptr 獨享全部權,不容許賦值、拷貝,boost::shared_ptr 是專門用於共享全部權的,因爲要共享全部權,其在內部使用了引用計數。boost::shared_ptr 也是用於管理單個堆內存對象的。
這個智能指針解決了auto_ptr獨佔的問題,採用引用計數的方法,一旦最後一個這樣的指針被銷燬,也就是一旦某個對象的引用計數變爲0,這個對象會被自動刪除。這在非環形數據結構中防止資源泄露頗有幫助。
用法:刪除共用對象
對應代碼:<SharedPtr>
#include<iostream> using namespace std; template<class T> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) , _pCount(new long(1)) {} SharedPtr() :_ptr(NULL) , _pCount(new long(1)) {} SharedPtr<T>(const SharedPtr<T>& sp) : _ptr(sp._ptr) , _pCount(sp._pCount) { ++(*_pCount); } SharedPtr<T>& operator=(const SharedPtr<T>& sp) { if (&sp != this) { if (--(*_pCount) == 0) // 減到0釋放對象一次 { delete _ptr; delete _pCount; } _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } return *this; } ~SharedPtr() { if (_ptr) { if (--(*_pCount) == 0) { delete _ptr; delete _pCount; } } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } long GetCount() { return *(_pCount); } T* GetPtr() { return _ptr; } private: T* _ptr; long* _pCount; }; void Test() { SharedPtr<int> sp1 = new int(1); SharedPtr<int> sp2 = sp1; SharedPtr<int> sp3 = new int(2); sp3 = sp1; } int main() { Test(); return 0; }
可是shared_ptr看起來完美,可是也存在一下問題:
1. 引用計數更新存在着線程安全
2.循環引用
3.定置刪除器
咱們先在這裏討論第二種狀況,即循環引用問題
咱們引出循環引用的場景圖:
此時,兩個節點的引用計數都爲2,由於有兩個指針分別指向a和b,此時就有了一個問題,_next析構時等着_prev析構,_prev要析構,只能b析構;而_prev也在等_next析構,_next要析構,只能a析構。這樣就有個問題相似」你等我,我等你,無休止,永遠不會析構,一直循環「。
要解決這個問題,就又要引出弱指針:weak_ptr
weak_ptr惟一的功能就是解決shared_ptr的循環引用問題,那是怎麼解決的呢?
咱們看代碼:
#include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> using namespace boost; struct ListNode { shared_ptr<ListNode > _prev; shared_ptr<ListNode > _next; //weak_ptr<ListNode > _prev; // 在此,weak_ptr能夠解決循環引用的問題 //weak_ptr<ListNode > _next; ~ ListNode() { cout<<"~ListNode()" <<endl; } }; void Test () { // 循環引用問題 shared_ptr <ListNode > p1( new ListNode ()); shared_ptr <ListNode > p2( new ListNode ()); cout <<"p1->Count:" << p1. use_count()<<endl ;// use_count是庫裏的引用計數 cout <<"p2->Count:" << p2. use_count()<<endl ; // p1節點的_next指向 p2節點 p1->_next = p2; // p2節點的_prev指向 p1節點 p2->_prev = p1; cout <<"p1->Count:" << p1. use_count ()<<endl ; cout <<"p2->Count:" << p2. use_count ()<<endl ; }
這樣問題就獲得瞭解決。
至此,咱們也知道了,當須要寫一個智能指針時,咱們儘量的去寫scoped_ptr或者shared_ptr,而千萬不要寫auto_ptr,問題上面已經詳細分解。
偏文不到之處,還請評正。