智能指针是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而没有增加引用计数~
博主要去了解尾大的民间组织写出的boost库辣~\(≧▽≦)/~
RAII&智能指针