智能指针——AutoPtr & ScopedPer & SharedPtr & WeakPtr
2017-03-02 20:36
302 查看
在说智能指针之前先说一下什么是RAII,RAII就是资源分配即初始化,这是一种思想,其最广泛的应用就是智能指针。
智能指针就是能自动完成资源的清理和释放。
为什么需要智能指针呢?因为有时候写的代码忘了进行指针所指向内存的清理工作,或者有时候代码的运行会提前return或者throw使得本来已经写好的清理代码未曾运行,比如下面的代码:
针对这种情况,当然是有办法来处理的,但是处理起来先对比较麻烦,对于抛异常的throw,可以去捕获异常然后进行释放空间,而用智能指针进行处理时则显得比较容易,因为智能指针只用定义,释放内存与清理工作交给了智能指针的模板类内的析构函数去完成,如此一来就显得方便许多,所以在C++11中就有ScopedPer (uniquePtr), SharedPtr 和 WeekPtr供我们使用,而AutoPtr是早期的一种智能指针,但是因为它是一个失败的设计,所以C++11中并没有收录,这里让我们来简单的看看这四种智能指针的实现机制。
(1)AutoPtr
AutoPtr是利用管理权的转移实现的,也就是说一个指针只管理一个内存,当前内存有别的指针指向的时候,原来指向这块内存指针就指向NULL,以此来实现智能管理,防止两次析构同一块内存或者有内存没有释放这种情况。
下面简单的看一下这种AutoPtr的模板类的模拟实现:
说白了就是专门写了一个不用自己去调用的析构函数,所以这是一个失败的设计。
(2)ScoptedPtr( 或者叫UniquePtr )
ScoptedPtr是一种防拷贝的智能指针,或者说不允许进行拷贝和赋值更为准确,因为它是将它的拷贝构造和赋值运算符的重载在类内只声明,但是并不定义,所以在用别的指针进行赋值或者拷贝构造时,就会编译不过,也就保证了所写的代码中一个指针就只管理一块内存储,就不会发生内存泄漏等问题。
下面来看模拟实现的ScoptedPtr模板类代码:
(3)SharedPtr
SharedPtr是利用引用计数实现的智能指针,引用计数就是在类的成员变量中再定义一个指针指向一块内存,这块内存中保存着指向_Ptr这个指针所管理的内存的指针个数,也就是说记录着有多少指针共同管理这块内存,当其中有指针不再指向这块内存时,只将引用计数减去1,当引用计数的值为1时,再将这块内存清理和释放,如此一来就实现了内存的重复利用和忘记释放的问题。
下面看一下Sharedptr的模拟实现:
★★SharedPtr的循环引用问题:
我们看到了SharedPtr的引用计数模拟实现代码,在析构函数中更可以清晰地看到,只有当引用计数的值为1时才会对这个指针指向的内存进行清理和释放,在实际使用当中,有时候在需要析构它的时候,它的引用计数并没有减为1,这就使得内存泄漏。
来看下面的代码(实际使用需要包头文件):
这里的代码产生了什么问题呢?我们来看一个简图:
我们看ptr1所指向的这块内存,由于是一个链表,所以在实际使用的过程中,它的后继节点会有指针指向它,也就是图中的绿色2号指针,如此一来它的引用计数就增加为2,而在析构的时候,由于ptr1是定义的智能指针,所以会自动完成清理工作,此时就是将它的引用计数值减去1,之后它的引用计数值变为1,对于ptr2指向的内存也是同样的道理,当ptr2 不在指向时,打的引用计数还是为1,如下图所示:
这里我们看到这块申请出来的内存就一直没有被析构,因为只有当它的引用计数值为1,并且要解除当前指针的指向关系时,它才会被析构,如图所示,左边的内存有ptr2->_prev指向,右边的内存有ptr1->_next指向,若想析构左边的内存,需要解除ptr2->_prev的指向,也就是ptr2->_prev被析构,若想析构右边的内存,需要解除ptr1->_next的指向,所以就陷入循环引用。
(4)WeakPtr
WeakPtr就是用来解决SharedPtr的循环引用问题的,它的实现原理是:只让指针指向,但是不增加引用计数。
也就是在使用的时候如下:
总结:智能指针的只用要包含相关的头文件:
然后就可以像普通指针一样使用,而且不用自己现实的去完成清理和释放工作,非常方便。
智能指针就是能自动完成资源的清理和释放。
为什么需要智能指针呢?因为有时候写的代码忘了进行指针所指向内存的清理工作,或者有时候代码的运行会提前return或者throw使得本来已经写好的清理代码未曾运行,比如下面的代码:
void Test() { int* ptr = new int(0); //1.throw //2.return delete ptr; }
针对这种情况,当然是有办法来处理的,但是处理起来先对比较麻烦,对于抛异常的throw,可以去捕获异常然后进行释放空间,而用智能指针进行处理时则显得比较容易,因为智能指针只用定义,释放内存与清理工作交给了智能指针的模板类内的析构函数去完成,如此一来就显得方便许多,所以在C++11中就有ScopedPer (uniquePtr), SharedPtr 和 WeekPtr供我们使用,而AutoPtr是早期的一种智能指针,但是因为它是一个失败的设计,所以C++11中并没有收录,这里让我们来简单的看看这四种智能指针的实现机制。
(1)AutoPtr
AutoPtr是利用管理权的转移实现的,也就是说一个指针只管理一个内存,当前内存有别的指针指向的时候,原来指向这块内存指针就指向NULL,以此来实现智能管理,防止两次析构同一块内存或者有内存没有释放这种情况。
下面简单的看一下这种AutoPtr的模板类的模拟实现:
template<class T> class AutoPtr { public: AutoPtr() :_Ptr(new T) {} AutoPtr(AutoPtr<T>& ap) :_Ptr(ap._Ptr) { //将传过来的指针值赋值给当前对象, //并将传过来的指针值赋值为空,防止产生野指针 ap._Ptr = NULL; } AutoPtr<T>& operator=(AutoPtr<T>& ap) { if (this != &ap) { //同样的处理 delete _Ptr; _Ptr = ap._Ptr; ap._Ptr = NULL; } return *this; } // *(解引用) 和 ->(指针操作符)的重载实现类外的指针使用 T* operator->() { return _Ptr; } T& operator*() { return *_Ptr; } T* GetPtr() { return _Ptr; } private: T* _Ptr; };
说白了就是专门写了一个不用自己去调用的析构函数,所以这是一个失败的设计。
(2)ScoptedPtr( 或者叫UniquePtr )
ScoptedPtr是一种防拷贝的智能指针,或者说不允许进行拷贝和赋值更为准确,因为它是将它的拷贝构造和赋值运算符的重载在类内只声明,但是并不定义,所以在用别的指针进行赋值或者拷贝构造时,就会编译不过,也就保证了所写的代码中一个指针就只管理一块内存储,就不会发生内存泄漏等问题。
下面来看模拟实现的ScoptedPtr模板类代码:
template<class T> class ScopedPtr { public: T* operator->() { return _Ptr; } T& operator*() { return *_Ptr; } T* GetPtr() { return _Ptr; } //将拷贝构造和赋值运算符的重载, //在ScoptedPtr类内声明为保护,并且只声明不定义 protected: ScopedPtr(ScopedPtr<T>& ap); ScopedPtr& operator=(ScopedPtr<T>& ap); T* _Ptr; };
(3)SharedPtr
SharedPtr是利用引用计数实现的智能指针,引用计数就是在类的成员变量中再定义一个指针指向一块内存,这块内存中保存着指向_Ptr这个指针所管理的内存的指针个数,也就是说记录着有多少指针共同管理这块内存,当其中有指针不再指向这块内存时,只将引用计数减去1,当引用计数的值为1时,再将这块内存清理和释放,如此一来就实现了内存的重复利用和忘记释放的问题。
下面看一下Sharedptr的模拟实现:
template<class T> class SharedPtr { public: SharedPtr(T* ptr) :_Ptr(ptr) ,_refcount(new size_t(1)) {} SharedPtr(SharedPtr<T>& ap) :_Ptr(ap._Ptr) ,_refcount(ap._refCount ) { ++(*_refcount); } ~SharedPtr() { if (--(*_refcount) == 0) { delete _Ptr; delete _refcount; } } SharedPtr<T>& operator=(SharedPtr<T>& ap) { if (this != &ap) { Relase(); _Ptr = ap._Ptr; _refcount = ap._refcount; ++(*_refcount); } } void Relase() { delete _Ptr; delete _refcount; } private: T* _Ptr; //智能指针 size_t* _refcount; //引用计数 };
★★SharedPtr的循环引用问题:
我们看到了SharedPtr的引用计数模拟实现代码,在析构函数中更可以清晰地看到,只有当引用计数的值为1时才会对这个指针指向的内存进行清理和释放,在实际使用当中,有时候在需要析构它的时候,它的引用计数并没有减为1,这就使得内存泄漏。
来看下面的代码(实际使用需要包头文件):
struct ListNode { boost::shared_ptr<ListNode> _prev; //前驱指针 boost::shared_ptr<ListNode> _next; //后继指针 }; void Test() { boost::shared_ptr<ListNode> ptr1(new ListNode()); boost::shared_ptr<ListNode> ptr2(new ListNode()); ptr1->next = ptr2; //ptr1的后继指针指向ptr2 ptr2->_prev = ptr1; //ptr2的前驱指针指向ptr1 }
这里的代码产生了什么问题呢?我们来看一个简图:
我们看ptr1所指向的这块内存,由于是一个链表,所以在实际使用的过程中,它的后继节点会有指针指向它,也就是图中的绿色2号指针,如此一来它的引用计数就增加为2,而在析构的时候,由于ptr1是定义的智能指针,所以会自动完成清理工作,此时就是将它的引用计数值减去1,之后它的引用计数值变为1,对于ptr2指向的内存也是同样的道理,当ptr2 不在指向时,打的引用计数还是为1,如下图所示:
这里我们看到这块申请出来的内存就一直没有被析构,因为只有当它的引用计数值为1,并且要解除当前指针的指向关系时,它才会被析构,如图所示,左边的内存有ptr2->_prev指向,右边的内存有ptr1->_next指向,若想析构左边的内存,需要解除ptr2->_prev的指向,也就是ptr2->_prev被析构,若想析构右边的内存,需要解除ptr1->_next的指向,所以就陷入循环引用。
(4)WeakPtr
WeakPtr就是用来解决SharedPtr的循环引用问题的,它的实现原理是:只让指针指向,但是不增加引用计数。
也就是在使用的时候如下:
struct ListNode { boost::weak_ptr<ListNode> _prev; //前驱指针 boost::weak_ptr<ListNode> _next; //后继指针 };
总结:智能指针的只用要包含相关的头文件:
然后就可以像普通指针一样使用,而且不用自己现实的去完成清理和释放工作,非常方便。
相关文章推荐
- C++智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> )
- 详解Boost库智能指针(shared_ptr && scoped_ptr && weak_ptr )
- [置顶] 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- shared_ptr&scoped_ptr&weak_ptr
- C++11智能指针(五):shared_ptr的循环引用的问题及weak_ptr
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- 浅析Boost智能指针:scoped_ptr shared_ptr weak_ptr
- 详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- shared_ptr & weak_ptr
- auto_ptr,scoped_ptr,shared_ptr,weak_ptr区别
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- auto_ptr,shared_ptr,weak_ptr,scoped_ptr
- C++学习之智能指针--auto_ptr、scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr
- 详解C++各种智能指针: auto_ptr, shared_ptr, weak_ptr, scoped_ptr
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
- auto_ptr scoped_ptr shared_ptr weak_ptr unique_ptr