您好,登錄后才能下訂單哦!
RAII:資源分配及初始化。但是這個翻譯并沒有顯示出這個慣用法的真正內涵。RAII的好處在于它提供了一種資源自動管理的方式,當出現異常,回滾等現象時,RAII可以正確的釋放資源。
內存泄漏會導致:
1.內存耗盡 2.其他程序可能用不了了 3.程序崩潰
在資源的獲取和釋放之間,我們通常會使用資源,但常常一些不可預計的異常會在資源使用過程中產生,這就使資源沒有得到正確的釋放。但是我們其實是可以捕捉異常的,但是捕捉異常會使得代碼冗余雜亂,量大而且可讀性比較低。
比如:
void DoSomething() { int *p=new int(1); cout<<"DoSomething()"<<endl; } void Test() { try { DoSomething(); } catch(...) { delete p; throw; } }
這樣短短一個代碼看起來卻很雜亂,而且也不好控制。
所以我們引出了智能指針。智能指針的實現原理就是RAII。
智能指針(smart pointer)是存儲指向動態分配(堆)對象指針的類,用于生存期控制,能夠確保自動正確的銷毀動態分配的對象,防止內存泄露。它的一種通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針并將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),并增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。
下面介紹幾個Boost庫中的智能指針。
AutoPtr
AutoPtr的實現主要是管理權轉移。它沒有考慮引用計數,當用一個對象構造另一個對象時,會轉移這種擁有關系。
template<class T> class AutoPtr { //friend ostream& operator<< <T>(ostream& os, const AutoPtr<T>& ap); public: AutoPtr(T* ptr) :_ptr(ptr) {} ~AutoPtr() { if (_ptr!=NULL) { delete _ptr; } } AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) { ap._ptr = NULL; } public: AutoPtr<T>& operator=(const AutoPtr<T>& ap) { if (this != ap) { delete _ptr; _ptr = ap._ptr; ap._ptr = NULL; return _ptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } public: T* GetPtr() { return _ptr; } private: T* _ptr; };
舊版的AutoPtr在它的成員函數中有一個變量owner,在構架對象的時候owner為true。當用它去構建新的對象時,他自己的owner變為false,新對象的owner為true。賦值重載的時候也是,新對象的owner是true。這樣在析構的時候只要判斷owner的狀態是否為true,當為true時,將這塊空間delete即可。
template<class T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) , owner(true) {} ~AutoPtr() { if (owner) { delete _ptr; } } AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) , owner(true) { ap._ptr = NULL; ap.owner = false; } public: AutoPtr<T>& operator=(const AutoPtr<T>& ap) { if (this != ap) { delete _ptr; _ptr = ap._ptr; ap.owner = false; owner = true; return _ptr; } } }
看起來舊的比新的好。AutoPtr最大的缺點就是只能有一個指針管理一塊空間。那么為什么新的還取代了舊的呢?看下面
AutoPtr<int> ap1(new int(1)); if (1) { AutoPtr<int> ap2(ap1); } *ap1 = 3;
這段代碼是用舊版本實現的智能指針(ap1)指向一個動態開辟的內存,然后在if條件語句中又有一個ap2指向這塊內存,我們會知道,根據舊版的智能指針的實現原理,ap1的_owner為false,ap2的_owner為true。那么除了if條件語句的局部作用域,ap2就自動調用析構函數釋放內存,那么當我們在外面*ap1=3時,訪問到的是一塊已經被釋放了的內存,那么程序這時就會出現問題。
如果是新版的auto_ptr,它提供了一個公有成員函數GetPtr(),可以獲取指針_ptr,當發生這種情況時,它可以先判斷_ptr是否為空,然后才去訪問內存。舊版本這樣做是無用的,因為ap1的_ptr并不為空。
2.ScopePtr//守衛指針
這個類型的指針簡單來說就是簡單粗暴的防拷貝。
template<class T> class ScopedPtr { public: ScopedPtr(T* ptr) :_ptr(ptr) {} ~ScopedPtr() { if (_ptr != NULL) { delete _ptr; } } protected: ScopedPtr(const ScopedPtr<T>& sp); ScopedPtr<T>& operator=(ScopedPtr<T>& sp); public: T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; };
將拷貝構造函數和賦值重載函數只聲明不實現,而且是Protected.就是為了防止在類外實現的情況發生,簡單粗暴哈哈。
3.SharedPtr
采用引用計數,構造一個對象時計數器為1,用這個對象去拷貝構造另一個新的對象時,計數器增加1.去賦值給另一個對象時,計數器同樣加1.析構時計數器減1.當計數器值為1時,便可delete這塊空間。
template<class T> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) , _pcount(new int(1)) {} ~SharedPtr() { _Release(); } SharedPtr(SharedPtr<T>& sp) { _ptr = sp._ptr; _pcount = sp._pcount; (*_pcount)++; } public: SharedPtr<T>& operator=(const SharedPtr<T>& sp) { if (_ptr != sp._ptr) { _Release(); _ptr = sp._ptr; _pcount = sp._pcount; (*_pcount)++; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } long UseCount() { return *(_pcount); } T* GetPtr() { return _ptr; } private: T* _ptr; int* _pcount; Del _del; void _Release() { if (--(*_pcount) == 0) { _del(_ptr); delete(_pcount); } } };
在這些指針中最常用到的就是SharedPtr了。但是SharedPtr也存在問題。
線程安全(這個問題我這個級別還不能很好地解決哈哈。等著我變成大神,就辦了它。。。)
循環引用
什么是循環引用呢??給小伙伴舉個例子來說明一下。
void test2() { SharedPtr<Node> cur(new Node(1)); SharedPtr<Node> next(new Node(2)); cur->_next = next; next->_prev = cur; } struct Node { Node(int d) :_data(d) , _next(NULL) , _prev(NULL) {} int _data; SharedPtr<Node> _next; SharedPtr<Node> _prev; }; int main() { test2(); getchar(); return 0; }
運行這段代碼回發現并沒有輸出。沒有輸出的原因是 cur,next的引用計數都是2,當出了test2的作用域時,分別調用析構函數,二者的引用計數都減為1.但是cur靠next->_prev指著,next靠cur->_next指著,兩個都在等對方先delete自己的引用計數才能減到0,自己才能delete,這就導致二者都不會delete了。要解決這個問題呢,我們就要引用第四種只能指針了。。那就是WeakPtr了。WeakPtr可以說就是為了SharedPtr準備的。因為WeakPtr的構造函數只接受SharedPtr類型的對象。
struct Node { Node(int d) :_data(d) , _next(NULL) , _prev(NULL) {} int _data; WeakPtr<Node> _next; WeakPtr<Node> _prev; };
WeakPtr不增加引用計數。這樣next->_prev和cur->_next兩個指針就不會增加引用計數,也就不會出現循環引用的問題了。
3.定制刪除器
當SharedPtr類型的指針遇到其他有些類型的指針時,它就不行了。。。。哦哦。。為什么說他不行了呢,因為SharedPtr只能解決new 出來的東西。那么對于文件類型的指針呢,malloc出來的東西呢。這就需要我們為他們量身定制解決方法啦。
在這里,又要講到另外一個知識點了。那就是仿函數。它就是能把對象當做函數調用一樣的使用。
template<class T> struct Less { bool operator()(const T& l,const T& r) { retrun l>r; } } int main() { Less less; cout<<less(1.2)<<endl;//使用對象去調用類里面的函數 }
接下來就是定制刪除器
template<class T ,class Del = Delete<T>> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) , _pcount(new int(1)) {} SharedPtr(T* ptr,Del del) :_ptr(ptr) , _pcount(new int(1)) , _del(del) {} ~SharedPtr() { _Release(); } SharedPtr(SharedPtr<T,Del>& sp) { _ptr = sp._ptr; _pcount = sp._pcount; (*_pcount)++; } public: SharedPtr<T, Del>& operator=(const SharedPtr<T, Del>& sp) { if (_ptr != sp._ptr) { _Release(); _ptr = sp._ptr; _pcount = sp._pcount; (*_pcount)++; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } long UseCount() { return *(_pcount); } T* GetPtr() { return _ptr; } private: T* _ptr; int* _pcount; Del _del; void _Release() { if (--(*_pcount) == 0) { _del(_ptr); delete(_pcount); } } }; template<class T> struct Free //free { void operator()(void* ptr) { cout << "free" << endl; free(ptr); ptr = NULL; } }; template<class T> struct Delete //delete { void operator()(T* ptr) { cout << "Del" << endl; delete(ptr); ptr = NULL; } }; template<class T> struct Fclose //fclose { void operator()(void* ptr) { cout << "Fclose" << endl; fclose((FILE*)ptr); } };
最后我們來總結一下:
AutoPtr 管理權轉移-》不要使用
ScopePtr 防拷貝-》簡單粗暴
SharedPtr 引用計數-》增減引用計數,最有一個對象釋放
xxxArray 管理對象數組-》operator[]
WearPtr 弱指針,輔助SharedPtr.解決循環引用
說了這么多,比較啰嗦哈
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。