为什么会有智能指针这个东西,智能指针又是什么东西。
由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。那么就有了智能指针这个东西。
智能指针就是基于RAII的要求使用模板类,自动化地管理动态资源的释放(不管理资源的创建),智能指针看上去是指针,实际上是赋予了定义的对象。智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
C++STL提供了4种智能指针,包括auto_ptr、unique_ptr(scoped_ptr)、shared_ptr和weak_ptr。
(1)auto_ptr
独占所有权,所有权转移
auto_ptr版本的智能指针采用的是管理权转移的方法,即由a构造出b对象之后,a对象置为NULL,即之后不能再访问a对象,只有b一个对象维护该空间。
模板auto_ptr是C++98提供的解决方案,C+11已将将其摒弃。,不推荐使用。
旧版本的auto_ptr,用bool值控制 旧库使用拥有者会导致野指针。
template<class T> class AutoPtr { public: AutoPtr(T *p = NULL) :_Ptr(p) , _owner(false) { if (_Ptr != NULL) { _owner = true; } } ~AutoPtr() { if (_owner) { delete _Ptr; _Ptr = NULL; } } AutoPtr(AutoPtr<T>& ap) :_Ptr(ap._Ptr) , _owner(ap._owner) { ap._owner = false; } AutoPtr& operator=(AutoPtr<T>& ap) { if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值, { if (_owner) delete _Ptr; _Ptr = ap._Ptr; _owner = ap._owner; //_owner = ture; ap._owner = false; } return *this; } T& operator*() { return *_Ptr; } T* operator->() { if (_owner) { return _Ptr; } return NULL; } T* Get() { if (_owner) { return _Ptr; } return NULL; } private: T* _Ptr; bool _owner; };新版本的auto_ptr
template<typename T> class AutoPtrNew { public: AutoPtrNew(T *p = NULL) :_Ptr(p) { } ~AutoPtrNew() { if (NULL != _Ptr) { delete _Ptr; _Ptr = NULL; } } AutoPtrNew(AutoPtrNew<T>& ap) :_Ptr(ap._Ptr) { ap._Ptr = NULL; } AutoPtrNew& operator=(AutoPtrNew<T>& ap) // 思路 :判断是不是自我赋值;然后判断左值是不是空,不空的话释左值; // 右值赋值给左值,右值置空 { if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值, { if (NULL != _Ptr) { delete _Ptr; } _Ptr = ap._Ptr; ap._Ptr = NULL; } return *this; } T& operator*() { return *_Ptr; } T* operator->() { return _Ptr; } T* Get() { return _Ptr; } private: T* _Ptr; };(2) scoped_ptr(unique_ptr) 独占所有权,防拷贝
scoped_ptr的实现原理是防止对象间的拷贝和赋值,即将拷贝构造和赋值运算符重载放入保护或私有的访问限定符内,只声明不定义防止他人在类外拷贝。简单粗暴地解决了auto_ptr的缺点,提高了代码的安全性,但是导致功能不完整。
unique_ptr 是 C++11 标识, ScopedPtr 是 boost库提供 还有ScopedAarray。
template<class T> class ScopedPtr { public: ScopedPtr(T * p = NULL) :_Ptr(p) { } ~ScopedPtr() { if (NULL != _Ptr) { delete _Ptr; _Ptr = NULL; } } T& operator*() { return *_Ptr; } T* operator->() { return _Ptr; } private: ScopedPtr(ScopedPtr<T>& sp);//只声明不实现,并且声明为私有,防止其他人实现并调用 ScopedPtr<T>& operator=(ScopedPtr<T>& sp); private: T* _Ptr; };(3)shared_ptr
shared_ptr的实现原理是通过引用计数(int *count)来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可。
template<class T> class SharedPtr { public: SharedPtr(T* p = NULL) :_Ptr(p) , _pCount(NULL) { if (NULL != _Ptr) { _pCount = new int(1); } } ~SharedPtr() //要判断计数器指针是否为空 { if (NULL != _pCount && --(*_pCount) == 0) { delete _Ptr; _Ptr = NULL; delete _pCount; _pCount = NULL; } } SharedPtr(SharedPtr<T>& sp) :_Ptr(sp._Ptr) , _pCount(sp._pCount) { if (NULL != _pCount) ++(*_pCount); } SharedPtr<T>& operator=(SharedPtr<T>& sp) //思路:判断自我赋值 左值_Ptr分三种情况 空 1个使用 多个使用 { if (_Ptr != sp._Ptr) { if (NULL == _Ptr) //空 { _Ptr = sp._Ptr; _pCount = sp._pCount; } else { if (--(*_pCount) == 0) //1 { delete _Ptr; delete _pCount; } _Ptr = sp._Ptr; _pCount = sp._pCount; } if (NULL != _pCount) (*_pCount)++; } return *this; } int UserCount() { return *_pCount; } T* operator->() { return _Ptr; } T& operator*() { return *_Ptr; } private: T* _Ptr; int* _pCount; };循环引用的问题。
就是指两个对象进行相互引用,造成相互制约的关系,导致智能指针不能释放的问题。
测试代码
//循环引用 #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> using namespace boost; struct ListNode { shared_ptr<ListNode > _prev; shared_ptr<ListNode > _next; //weak_ptr<ListNode > _prev; //weak_ptr<ListNode > _next; ~ListNode() { cout << "~ListNode()" << endl; } }; void Test() { // 循环引用问题 shared_ptr <ListNode > p1(new ListNode()); shared_ptr <ListNode > p2(new ListNode()); cout << "p1->Count:" << p1.use_count() << endl; cout << "p2->Count:" << p2.use_count() << endl; // p1节点的_next指向 p2节点 p1->_next = p2; // p2节点的_prev指向 p1节点 p2->_prev = p1; cout << "p1->Count:" << p1.use_count() << endl; //2 cout << "p2->Count:" << p2.use_count() << endl; //2 }通过上面的代码结合图可以知道,p1指针的count =2,(一个是自己本身对象,另一个是p2中的prev指向p1对象),同样的p2指针的count = 2;
这样当函数运行完结束的时候,p2先释放,count = 1,没有被释放,p1再释放,count = 1,没有被释放;而且,p1对象的释放是由p2的prev指向p1的,p1的释放,需要等待p2的释放,才能完成p1的释放,同理p2的释放也需要p1的释放,才能完成自己的p2的释放。
这样的话,它们互相等待,悲剧就发生了,两个都无法释放,
循环引用–使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)
weak_ptr是一个弱引用,只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它。 解决循环引用。
template<typename T> struct ListNode{ T _value; weak_ptr<ListNode> _prev; weak_ptr<ListNode> _next; ListNode(const T & value) :_value(value) ,_prev(NULL) ,_next(NULL){} ~ListNode(){ std::cout<<"~ListNode()"<<std::endl; } }; void TestWeekPtr(){ std::shared_ptr<ListNode<int>> sp1(new ListNode<int>(10)); std::shared_ptr<ListNode<int>> sp2(new ListNode<int>(20)); sp1->_next = sp2; sp2->_prev = sp1; std::cout<<sp1.use_count()<<std::endl; std::cout<<sp2.use_count()<<std::endl; }定制删除器
由于每种指针释放资源的方式不同,比如数组用delete[]释放资源,文件用fclose关闭等。我们需要针对不同的资源选择合适的释放方式,定制删除器便是通过仿函数来完成该功能。
#include<memory> #include<iostream> using namespace std; class Fclose1 { public: void operator () (FILE *& fp) { if (NULL != fp) { fclose(fp); fp = NULL; } } }; void Fclose(FILE* fp) { if (NULL != fp) { fclose(fp); fp = NULL; } } void FunTest() { FILE* fp = fopen("1.txt", "w"); shared_ptr<FILE> sp1(fp, Fclose1());//仿函数 shared_ptr<FILE> sp2(fp, Fclose);//函数指针 }