智能指針是C++中爲了實現資源的有效管理而被提出的,我們可以創建它但無須操心它的釋放問題,在引入異常機制的程序裏它是十分有用的,或者說,對於博主這中粗心大意的人來說還是可以偶爾使用的。他可以在一些場合防止內存泄漏的問題。但是,智能指針也是存在着許多的問題,所以許多的編程規範裏告誡我們少使用智能指針,但對於我們來說,必須瞭解它的原理。
*RAII:資源獲得即初始化,我們在構造函數裏將其初始化,並在析構函數裏釋放它
eg:一個簡單的AutoPtr的實現
template<class T> class AutoPtr { friend ostream& operator <<(ostream&os, const AutoPtr& ap) { os << ap._ptr; return os; } public: AutoPtr(T * p) :_ptr(p) {} AutoPtr(AutoPtr & ap) { _ptr = ap._ptr; ap._ptr = NULL; } AutoPtr& operator =(AutoPtr & ap) { if (ap._ptr != _ptr) { delete _ptr; _ptr = ap._ptr; ap._ptr = NULL; } return *this; } T& operator *() { return *_ptr; } T* operator ->() { return _ptr; } ~AutoPtr() { if (_ptr != NULL) { delete _ptr; } } private: T* _ptr; };
我們可以看出,上面的程序問題在哪裏呢,我們力求來模仿原生指針讓多個AutoPtr管理同一塊空間但是更加嚴重的問題出現:我們將管理權限僅交給最後一個AutoPtr,但是如果有的客戶程序員們不瞭解這個機制而將其當做普通的指針來使用,如果他使用了沒有管理權限的指針將發生錯誤!
爲了避免這樣的錯誤出現,我們使用了一種簡單暴力的方拷貝機制處理從而使得ScopedPtr粗線啦
template<class T> class ScopedPtr { public: ScopedPtr(T* ptr) :_ptr(ptr) {} ~ScopedPtr() { if (_ptr != NULL) delete _ptr; } T& operator *() { return *_ptr; } T* operator ->() { return _ptr; } private: T* _ptr; ScopedPtr(ScopedPtr<T>& sp); ScopedPtr& operator =(ScopedPtr<T>& sp); };
爲了避免這種錯誤發現,我們可以直接拒絕客戶程序員做拷貝構造和賦值操作,使得我們的程序變得更加安全,遺憾的是,這樣我們的指針就不能做到和原生指針一樣可以使用多個指針共同維護一塊空間了。
*防拷貝的實現:把拷貝構造及賦值運算符的重載聲明成私有/保護成員,並且只聲明不定義。
然而這樣的智能指針似乎還是不太理想。所以我們又發現,可以使用引用計數的方法使得我們的指針能更加像原生指針的行爲,於是,我們的SharedPtr出現
template <class T> class SharedPtr { public: SharedPtr(T * p) :_ptr(p), _pCount(new int(1)) { } SharedPtr(SharedPtr& sp) { _ptr = sp._ptr; (*sp._pCount)++; _pCount = sp._pCount; } SharedPtr& operator=(SharedPtr sp) { swap(_ptr,sp._ptr); swap(_pCount, sp._pCount); return *this; } T* operator ->() { return _ptr; } T& operator *() { return *_ptr; } ~SharedPtr() { if (--(*_pCount) == 0) { delete _ptr; delete _pCount; } } int GetCount() { return *_pCount; } private: T * _ptr; int *_pCount; };
(以上代碼爲現代寫法,如果這個時候你還是挺閒的就別忘記把傳統寫法也實現下啵)
是不是覺得有點贊,不得不說,想出這些方法的大大們還是棒棒的。
其實,我們首先介紹的AutoPtr的實現方法還有另一種,不過現在大家已經基本拋棄了這種寫法。
eg:AutoPtr實現方法二
#include<iostream> //*************AutoPtrWithOwner*********** template<class T> class AutoPtrWithOwner { public: AutoPtrWithOwner(T* ptr) :_ptr(ptr), _owner(true) {} ScopedPtrWithOwner(AutoPtrWithOwner& sp) { _ptr = sp._ptr; _owner = true; sp._owner = false; } AutoPtrWithOwner& operator=(AutoPtrWithOwner& sp) { if (!sp._owner) { std::cout << "實參沒有管理權限!請勿非法操作!" << std::endl; } else if (_ptr != sp._ptr) { if (_owner == true) delete _ptr; _ptr = sp._ptr; _owner = true; sp._owner = false; } return *this; } ~AutoPtrWithOwner() { if (_owner) delete _ptr; } private: T *_ptr; bool _owner; };
相比於我們介紹的第一種方式,似乎這種方式的安全性並不高,它和產生垂懸指針哦,就是我們所說的野指針。所以請你儘量不要使用這種方式。
我們看到上面的例子中已經介紹了三種智能指針
那麼博主都認識哪些智能指針呢?
1.auto ptr 實現:管理權限的轉移
2.shared ptr 實現:引用計數
3.scoped ptr 實現:防止拷貝
4.weak ptr (弱指針,用於輔助shared ptr)
實際上shared ptr仍然存在許多的缺陷,它可能引入:線程安全問題,循環引用問題,刪除器不匹配
*線程安全:百度百科
今天我們主要爲大家解決後兩個問題(其實是因爲博主還沒有學習多線程編程,不會啦,夭壽啊!)
1.定製刪除器(使用模板實現)
#include<iostream> #define _CRT_SECURE_NO_WARNINGS 1 //**************定製刪除器的實現*********** template<class T,class Del=Delete<T> > class SharedPtr { public: /*SharedPtr(T *ptr, Del del) :_ptr(ptr), _del(del), _pCount(new int(1)) {}*/ SharedPtr(T *ptr) :_ptr(ptr), _pCount(new int(1)) {} SharedPtr(const SharedPtr& sp) { _ptr = sp._ptr; _pCount = sp._pCount; (*_pCount)++; } SharedPtr& operator =(SharedPtr sp) { swap(_ptr, sp._ptr); swap(_ptr, sp._pCount); return *this; } ~SharedPtr() { _Relese(); } private: T *_ptr; T *_pCount; Del _del; void _Relese() { if (--(*_pCount) == 0) { _del(_ptr); _del(_pCount); } } }; template <class T> struct Free { void operator() (void *sp) { free(sp); sp = NULL; } }; template <class T> struct Delete { void operator() (const T*sp) { delete sp; } }; template <class T> struct Fclose { void operator() (void *sp) { fclose(sp); sp = NULL; } }; void testSharePtrDelete() { SharedPtr<int> sp1(new int(5)); SharedPtr<int> sp2(sp1); } void testSharePtrFree() { SharedPtr<int,Free<int>> sp1((int *)malloc(sizeof(int)*10)); SharedPtr<int,Free<int>> sp2(sp1); } void testSharePtrFclose() { FILE *pf = fopen("","r"); SharedPtr<FILE, Fclose<FILE>> sp1(pf); SharedPtr<FILE, Fclose<FILE>> sp2(sp1); }
*仿函數:使用operator()使得對象方法的調用形式像一般的函數一樣
2.循環引用
什麼是循環引用嘞?我們來舉栗子好了
Struct Node { shared_ptr<Node> _next; shared_ptr<Node> _prev; int _data; Node(int a):_data(a),_next(NULL),_prev(NULL) {} ~Node() { cout<<"~Node()"<<endl; } }; void Test() { shared_ptr<Node> cur(new Node(1)); shared_ptr<Node> next(new Node(2)); cur -> _next = next; next -> _prev = cur; }
上面的程序就出現了循環引用的問題我們可以看見我們調用之後並沒有調用析構函數,這樣就引起了內存泄漏的問題,爲什麼呢,因爲其每一個節點一開始被一個shared_ptr指着,後來你在實例化之後因爲他又被他前面或者後面的節點所指,引用計數發生了增加,所以在析構的時候檢查到自己的引用計數沒有減到0所以沒有釋放本來該釋放的空間。
其實這個問題是很好解決的,我們將Node裏面的shared_ptr換成weak_ptr就可以完美解決,在此處,weak_ptr輔助了shared_ptr而沒有增加引用計數~