c++智能指针的不断演化
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++智能指针的不断演化相关的知识,希望对你有一定的参考价值。
RAII
RAII资源分配即初始化,定义一个类来封装资源的分配和释放,在构造 函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
智能指针的引入:
由于return ,throw等关键字的存在,导致顺序执行流的错乱,不断的进行跳转,使开辟的空间
看似被释放,而实际上导致内存的泄露。
例如以下两个例子:
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 ; }
以上两个例子,看似new与delete结合使用,但是实际上已经导致内存的泄露,为了解决以上问题,就需要在写此类代码时需要多加小心,但是对于大量的代码开发,想要注意就很难了。
于是乎就引入了智能指针:
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。
c++库函数中的智能指针为(auto_ptr,unique_ptr,shared_ptr);
Boost库函数中的智能指针为(auto_ptr,scoped_ptr,shared_ptr)。
接下来就一起来了解一下智能指针的发展历史。
1、在vc6.0之前的版本中auto_ptr的模拟实现方式
template<class T> class OldAutoPtr { public: OldAutoPtr(T *ptr) :_ptr(ptr) , _owner(true) {} //拷贝构造 OldAutoPtr(OldAutoPtr& ap) :_ptr(ap._ptr) ,_owner(ap._owner) { ap._owner = false;//权限的转让 } //运算符重载 OldAutoPtr& operator=(OldAutoPtr& ap) { if (ap._ptr != _ptr)//自赋值和指向同一份空间 { if (_ptr) { delete _ptr; } _ptr = ap._ptr; _owner = ap._owner;//权限的转让 ap._owner = false; } return *this; } //析构函数 ~OldAutoPtr() { if (_owner)//只删除拥有权限的指针 { delete _ptr; } } public: T& operator *() { return *_ptr; } T* operator ->() { return _ptr; } private: T* _ptr; bool _owner;//权限拥有者 };
评价:
看似最初版本的auto_ptr已经很接近了原声指针的使用,多个指针都可以指向一份空间,而且释放的同时不会导致同一份空间的多次释放。但是在下面的情境中却发生了致命的危害:
void TestOldAutoPtr() { OldAutoPtr<int> ap1(new int(1)); if (true) { OldAutoPtr<int> ap2(ap1); //OldAutoPtr<int> ap3(new int(2)); //ap3 = ap2; } //ap1将析构的权限给予了ap2,ap1,ap2指向同一份空间,ap2在出了if作用域之后ap2对象释放,进而导致ap1也被释放。 //但是在if作用域之外,又对ap1(ap2)指向的空间进行简引用,导致程序崩溃,如果不使用的话则会造成指针的悬挂(野指针)。 *ap1 = 10; }
针对以上的状况,在之后的版本中对auto_ptr进行了完全的权限转移,转移之后该指针不能使用。
2、现在的版本auto_ptr的实现
template<typename T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) {} AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) { ap._ptr = NULL;//权限转移 } AutoPtr<T>& operator=(AutoPtr<T> & ap) { if (this != &ap)//自赋值 { if (_ptr) { delete _ptr; } _ptr = ap._ptr; ap._ptr = NULL;//权限转移 } return *this; } ~AutoPtr() { if (_ptr != NULL) { delete _ptr; _ptr = NULL; } } public: T& operator * ()//没有参数 { return *_ptr; } T* operator->()//没有参数 { return _ptr; } private: T* _ptr; };
评价:
这种实现方式很好的解决了old的版本特殊场景的野指针问题,但是它与原生指针的差别更大,由于实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易导致对于NULL指针的截引用,导致程序崩溃,其危害也是比较大的。
3、针对以上的问题,在Boost库中引入了简单粗暴的解决方法scoped_ptr,直接不允许其拷贝构造和赋值(ps:新的C++11标准中叫做unique_ptr)
解决办法:将赋值和拷贝构造函数只声明,不实现,切记,将其声明为protected或者private以免在类的外部对其进行破环性处理,同时也是提高了代码的安全性。
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; };
评价:
在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,该只能指针完全可以满足需求。
4、允许拷贝构造和赋值的shared_ptr模拟实现
解决办法:
通过为每个空间多开辟4个字节做为引用计数器,在拷贝构造、赋值、析构时用计数器来解决。
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; };
评价:
简化版智能指针SharedPtr看起来不错,但是实际上存在以下问题:
1)循环引用
2)定置删除器
循环引用
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()//循环引用 { 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);*/ }
解决办法:
使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)
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()//循环引用 { 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开辟的空间,而对于malloc,以及fopen打开的文件指针不能处理,为了能够全面的处理各种各样的指针,所以提出了定制删除器,而其实现则是通过仿函数(通过对()运算符的重载)来实现。
1)模拟实现的解决
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); }
测试结果
2)系统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());//崩溃 }
测试结果
总结:
1、如果需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器对象,应该只使用shared_ptr,任何情况下都不要使用auto_ptr.
2、使用时尽量局部化,因为其是通过调用其析构函数来实现资源的回收。
以上是关于c++智能指针的不断演化的主要内容,如果未能解决你的问题,请参考以下文章