智能指針是C++中爲了實現資源的有效管理而被提出的,咱們能夠建立它但無須操心它的釋放問題,在引入異常機制的程序裏它是十分有用的,或者說,對於博主這中粗枝大葉的人來講仍是能夠偶爾使用的。他能夠在一些場合防止內存泄漏的問題。可是,智能指針也是存在着許多的問題,因此許多的編程規範裏告誡咱們少使用智能指針,但對於咱們來講,必須瞭解它的原理。ios
*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); };
爲了不這種錯誤發現,咱們能夠直接拒絕客戶程序員作拷貝構造和賦值操做,使得咱們的程序變得更加安全,遺憾的是,這樣咱們的指針就不能作到和原生指針同樣可使用多個指針共同維護一塊空間了。ide
*防拷貝的實現:把拷貝構造及賦值運算符的重載聲明成私有/保護成員,而且只聲明不定義。函數
然而這樣的智能指針彷佛仍是不太理想。因此咱們又發現,可使用引用計數的方法使得咱們的指針能更加像原生指針的行爲,因而,咱們的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; };
(以上代碼爲現代寫法,若是這個時候你仍是挺閒的就別忘記把傳統寫法也實現下啵)this
是否是以爲有點贊,不得不說,想出這些方法的大大們仍是棒棒的。url
其實,咱們首先介紹的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而沒有增長引用計數~