您好,登錄后才能下訂單哦!
RAII資源分配即初始化,定義一個(gè)類來封裝資源的分配和釋放,在構(gòu)造 函數(shù)完成資源的分配和初始化,在析構(gòu)函數(shù)完成資源的清理,可以保證資源的正確初始化和釋放。
智能指針的引入:
由于return ,throw等關(guān)鍵字的存在,導(dǎo)致順序執(zhí)行流的錯(cuò)亂,不斷的進(jìn)行跳轉(zhuǎn),使開辟的空間
看似被釋放,而實(shí)際上導(dǎo)致內(nèi)存的泄露。
例如以下兩個(gè)例子:
void Test1() { int *p1 = new int(1); if (1) { return; } delete p1; } void DoSomeThing() { //... throw 2 ; //... } void Test2 () { int* p1 = new int(2); //... try { DoSomeThing(); } catch(...) { //delete p1 ; throw; } //... delete p1 ; }
以上兩個(gè)例子,看似new與delete結(jié)合使用,但是實(shí)際上已經(jīng)導(dǎo)致內(nèi)存的泄露,為了解決以上問題,就需要在寫此類代碼時(shí)需要多加小心,但是對(duì)于大量的代碼開發(fā),想要注意就很難了。
于是乎就引入了智能指針:
所謂智能指針就是智能/自動(dòng)化的管理指針?biāo)赶虻膭?dòng)態(tài)資源的釋放。
c++庫函數(shù)中的智能指針為(auto_ptr,unique_ptr,shared_ptr);
Boost庫函數(shù)中的智能指針為(auto_ptr,scoped_ptr,shared_ptr)。
接下來就一起來了解一下智能指針的發(fā)展歷史。
1、在vc6.0之前的版本中auto_ptr的模擬實(shí)現(xiàn)方式
template<class T> class OldAutoPtr { public: OldAutoPtr(T *ptr) :_ptr(ptr) , _owner(true) {} //拷貝構(gòu)造 OldAutoPtr(OldAutoPtr& ap) :_ptr(ap._ptr) ,_owner(ap._owner) { ap._owner = false;//權(quán)限的轉(zhuǎn)讓 } //運(yùn)算符重載 OldAutoPtr& operator=(OldAutoPtr& ap) { if (ap._ptr != _ptr)//自賦值和指向同一份空間 { if (_ptr) { delete _ptr; } _ptr = ap._ptr; _owner = ap._owner;//權(quán)限的轉(zhuǎn)讓 ap._owner = false; } return *this; } //析構(gòu)函數(shù) ~OldAutoPtr() { if (_owner)//只刪除擁有權(quán)限的指針 { delete _ptr; } } public: T& operator *() { return *_ptr; } T* operator ->() { return _ptr; } private: T* _ptr; bool _owner;//權(quán)限擁有者 };
評(píng)價(jià):
看似最初版本的auto_ptr已經(jīng)很接近了原聲指針的使用,多個(gè)指針都可以指向一份空間,而且釋放的同時(shí)不會(huì)導(dǎo)致同一份空間的多次釋放。但是在下面的情境中卻發(fā)生了致命的危害:
void TestOldAutoPtr() { OldAutoPtr<int> ap1(new int(1)); if (true) { OldAutoPtr<int> ap2(ap1); //OldAutoPtr<int> ap3(new int(2)); //ap3 = ap2; } //ap1將析構(gòu)的權(quán)限給予了ap2,ap1,ap2指向同一份空間,ap2在出了if作用域之后ap2對(duì)象釋放,進(jìn)而導(dǎo)致ap1也被釋放。 //但是在if作用域之外,又對(duì)ap1(ap2)指向的空間進(jìn)行簡引用,導(dǎo)致程序崩潰,如果不使用的話則會(huì)造成指針的懸掛(野指針)。 *ap1 = 10; }
針對(duì)以上的狀況,在之后的版本中對(duì)auto_ptr進(jìn)行了完全的權(quán)限轉(zhuǎn)移,轉(zhuǎn)移之后該指針不能使用。
2、現(xiàn)在的版本auto_ptr的實(shí)現(xiàn)
template<typename T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) {} AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) { ap._ptr = NULL;//權(quán)限轉(zhuǎn)移 } AutoPtr<T>& operator=(AutoPtr<T> & ap) { if (this != &ap)//自賦值 { if (_ptr) { delete _ptr; } _ptr = ap._ptr; ap._ptr = NULL;//權(quán)限轉(zhuǎn)移 } return *this; } ~AutoPtr() { if (_ptr != NULL) { delete _ptr; _ptr = NULL; } } public: T& operator * ()//沒有參數(shù) { return *_ptr; } T* operator->()//沒有參數(shù) { return _ptr; } private: T* _ptr; };
評(píng)價(jià):
這種實(shí)現(xiàn)方式很好的解決了old的版本特殊場景的野指針問題,但是它與原生指針的差別更大,由于實(shí)現(xiàn)了完全的權(quán)限轉(zhuǎn)移,所以導(dǎo)致在拷貝構(gòu)造和賦值之后只有一個(gè)指針可以使用,而其他指針都置為NULL,使用很不方便,而且還很容易導(dǎo)致對(duì)于NULL指針的截引用,導(dǎo)致程序崩潰,其危害也是比較大的。
3、針對(duì)以上的問題,在Boost庫中引入了簡單粗暴的解決方法scoped_ptr,直接不允許其拷貝構(gòu)造和賦值(ps:新的C++11標(biāo)準(zhǔn)中叫做unique_ptr)
解決辦法:將賦值和拷貝構(gòu)造函數(shù)只聲明,不實(shí)現(xiàn),切記,將其聲明為protected或者private以免在類的外部對(duì)其進(jìn)行破環(huán)性處理,同時(shí)也是提高了代碼的安全性。
template<typename T> class ScopedPtr { public: ScopedPtr(T* ptr) :_ptr(ptr) {} ~ScopedPtr() { if (_ptr != NULL) { delete _ptr; } } protected://將其聲明為protected或者private不能聲明為public ScopedPtr(ScopedPtr<T>& sp); ScopedPtr<T>operator=(ScopedPtr<T>&sp); public: T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T *_ptr; };
評(píng)價(jià):
在一般的情況下,如果不需要對(duì)于指針的內(nèi)容進(jìn)行拷貝,賦值操作,而只是為了防止內(nèi)存泄漏的發(fā)生,該只能指針完全可以滿足需求。
4、允許拷貝構(gòu)造和賦值的shared_ptr模擬實(shí)現(xiàn)
解決辦法:
通過為每個(gè)空間多開辟4個(gè)字節(jié)做為引用計(jì)數(shù)器,在拷貝構(gòu)造、賦值、析構(gòu)時(shí)用計(jì)數(shù)器來解決。
template<typename T> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) , _pCount(new int(1)) {} SharedPtr(const SharedPtr<T>& sp) { _ptr = sp._ptr; _pCount = sp._pCount; (*_pCount)++; } SharedPtr<T>operator=(SharedPtr<T> sp) { swap(_ptr, sp._ptr); swap(_pCount, sp._pCount); return *this; } ~SharedPtr() { _Realse(); } public: void _Realse() { if (--(*_pCount) == 0) { delete _ptr; delete _pCount; } } public: T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; int* _pCount; };
評(píng)價(jià):
簡化版智能指針SharedPtr看起來不錯(cuò),但是實(shí)際上存在以下問題:
1)循環(huán)引用
2)定置刪除器
循環(huán)引用
template<class T> struct Node { public: ~Node() { cout << "delete:" << this << endl; } public: shared_ptr<Node> _prev; shared_ptr<Node> _next; /*weak_ptr<Node> _prev; weak_ptr<Node> _next;*/ }; void test_round()//循環(huán)引用 { shared_ptr<Node> cur(new Node()); shared_ptr<Node> next(new Node()); cout << "連接前:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; cur->_next = next; next->_prev = cur; cout << "連接后:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; /*shared_ptr<Node> cur(new Node()); weak_ptr<Node> wp1(cur);*/ }
解決辦法:
使用一個(gè)弱引用智能指針(weak_ptr)來打破循環(huán)引用(weak_ptr不增加引用計(jì)數(shù))
template<class T> struct Node { public: ~Node() { cout << "delete:" << this << endl; } public: //shared_ptr<Node> _prev; //shared_ptr<Node> _next; weak_ptr<Node> _prev; weak_ptr<Node> _next; }; void test_round()//循環(huán)引用 { shared_ptr<Node> cur(new Node()); shared_ptr<Node> next(new Node()); cout << "連接前:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; cur->_next = next; next->_prev = cur; cout << "連接后:" << endl; cout << "cur:" << cur.use_count() << endl; cout << "next:" << next.use_count() << endl; /*shared_ptr<Node> cur(new Node()); weak_ptr<Node> wp1(cur);*/ }
定置刪除器
在shared_ptr中只能處理釋放new開辟的空間,而對(duì)于malloc,以及fopen打開的文件指針不能處理,為了能夠全面的處理各種各樣的指針,所以提出了定制刪除器,而其實(shí)現(xiàn)則是通過仿函數(shù)(通過對(duì)()運(yùn)算符的重載)來實(shí)現(xiàn)。
1)模擬實(shí)現(xiàn)的解決
template<typename T,typename D> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr) , _pCount(new int(1)) {} SharedPtr(T* ptr, D del) :_ptr(ptr) , _pCount(new int(1)) , _del(del) {} SharedPtr(const SharedPtr<T, D>& sp) { _ptr = sp._ptr; _pCount = sp._pCount; (*_pCount)++; } SharedPtr<T, D>operator=(SharedPtr<T, D> sp) { swap(_ptr, sp._ptr); swap(_pCount, sp._pCount); return *this; } ~SharedPtr() { _Realse(); } public: void _Realse() { if (--(*_pCount) == 0) { _del(_ptr); delete _pCount; } } public: T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; int* _pCount; D _del; }; template<typename T> struct DeafaultDel { void operator()(T* ptr) { cout << "DeafaultDel:" << ptr << endl; } }; template<typename T> struct Free { void operator()(T*ptr) { cout << "Free:" << ptr << endl; } }; template<typename T> struct Fclose { void operator()(T* ptr) { cout << "Fclose:" << ptr << endl; fclose(ptr); } }; //測試代碼 void TestDelete() { int *p1 = (int*)malloc(sizeof(int)); SharedPtr<int,Free<int>> sp1(p1); FILE* p2 = fopen("test.txt", "r"); SharedPtr<FILE, Fclose<FILE>> sp2(p2); }
測試結(jié)果
2)系統(tǒng)shared_ptr的解決
struct Free { void operator()(void * ptr) { cout << "Free:" << ptr << endl; free(ptr); } }; struct Fclose { void operator ()(FILE* ptr) { cout << "Fclose" << ptr << endl; fclose(ptr); } }; //測試代碼 void test_shared_ptr_delete() { int *p1 = (int*)malloc(sizeof(int)); shared_ptr<int> sp1(p1,Free()); FILE* p2 = fopen("test.txt", "r"); shared_ptr<FILE> sp2(p2,Fclose());//崩潰 }
測試結(jié)果
總結(jié):
1、如果需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,例如對(duì)STL容器對(duì)象,應(yīng)該只使用shared_ptr,任何情況下都不要使用auto_ptr.
2、使用時(shí)盡量局部化,因?yàn)槠涫峭ㄟ^調(diào)用其析構(gòu)函數(shù)來實(shí)現(xiàn)資源的回收。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。