四种智能指针的用法和原理

    xiaoxiao2021-03-25  387

      智能指针共分为4种,即boost库中的auto_ptr、scoped_ptr、shared_ptr、weak_ptr。   智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。   操作的方法:在构造函数时,将类外需要管理的指针传进去,可以给个空的缺省值,然后重载“->”“*”“=”…等符号,然后在析构函数时,释放这个指针空间,形成对这个指针的智能的管理。

    auto_ptr:

      转移资源。原理和操作方法如上所说,但是存在这种情况: int a=10; int *p=&a; auto_ptr p1(p); auto_ptr p2(p1);   此时,p1这个智能指针就无法管理p这块内存内存空间了,因为在auto_ptr的拷贝构造函数中是这样实现的: auto_ptr(auto_ptr& p) :_ptr(p._ptr) {   _ptr=NULL; }   这种情况下,原来的智能指针p1就无法再对p操作,并且在最终函数结尾,执行析构函数的时候,还会对p1进行析构,即使此时p1保存的是一个空指针,但是释放造成的开销也是不必要的。并且:auto_ptr存在以下几个缺陷: 1>不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;

    //正确情况: int i=new int(1); //堆上的空间——动态开辟 auto_ptr<int> ap1(&i); //错误情况: int i=1; //栈上的空间 auot_ptr<int> ap2(&i);

    2>不要使用两个auto_ptr指针指向同一个指针,具体原因上面解释过; 3>不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配; 4>不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。   还有最重要的一点就是,什么情况下也别使用auto_ptr智能指针。

    scoped_ptr:

    template<typename T> class ScopedPtr { public: explicit ScopedPtr(T *p=0):mp(p) { } ~ScopedPtr() { delete mp; } void reset(T *p=0) { if(mp!=p) { delete mp; mp=p; } } T &operator*() const { if(mp!=0) return *mp; else throw std::runtime_error("the pointer is null"); } T *operator->() const { if(mp!=0) return mp; else throw std::runtime_error("the pointer is null"); } T *get() const { return mp; } void swap(ScopedPtr &rhs) { T *temp=mp; mp=rhs.mp; rhs.mp=temp; } private: ScopedPtr(const ScopedPtr& rhs); ScopedPtr &operator=(const ScopedPtr& rhs); T *mp; };

      可以看见最大的不同就是scoped_ptr没有给出拷贝构造和赋值运算符的重载运算符的定义,只给了private下的声明,即表明scoped_ptr智能指针无法使用一个对象创建另一个对象,也无法采用赋值的形式。这无疑提升了智能指针的安全性,但是又存在无法“++”、“–”这些操作,当然也多了“*”、“->”这两种操作。所以这种形式叶并不是最完美的。所以又有了shared_ptr。

    shared-ptr:

    template<class T> class my_shared_ptr{ public: my_shared_ptr(T* pt){ _pBlock = new ControlBlock(pt); } my_shared_ptr(const my_shared_ptr& rhs){ _pBlock = rhs._pBlock; _pBlock->ref_count++; } ~my_shared_ptr(){ _pBlock->ref_count --; if(0 == _pBlock->ref_count) delete _pBlock; } my_shared_ptr& operator=(const my_shared_ptr& rhs){ if(this == &rhs) return *this; _pBlock->ref_count --; if(0 == _pBlock->ref_count) delete _pBlock; _pBlock = rhs._pBlock; _pBlock->ref_count++; } T* get(){return _pBlock->_ptr;} T* operator->(){return _pBlock->_ptr;} void reset(T* pt){ _pBlock->ref_count --; if(0 == _pBlock->ref_count) delete _pBlock; _pBlock = new ControlBlock(pt); } private: struct ControlBlock{ int ref_count; T* _ptr; ControlBlock(T* pt){ _ptr = pt; ref_count = 1; } ~ControlBlock(){ delete _ptr; } }; ControlBlock* _pBlock; };

      shared_ptr和以上二者的最大区别就是他维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针),在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。其实,这个原理就和string类的浅拷贝是一样的。   这个类型的智能指针在一定程度上让我们的管理得到了很大的便捷和省心。可以和其他共同管理这块空间的智能指针一起修改这块空间储存的值,达到“智能”的效果。   那么这样一切就完美了吗?答案是:No!因为还存在这种情况:当管理的每一个指针都是一个双向链表的指针时,那么此时我们的析构函数存在一个很大的问题。   现在我们假设一种最简单的情况,这个双向链表中只有两个节点,并且p1的prev和p2的next都指向空。但注意,这时p1本身管理一段空间,p2的prev也管理p1管理的这块空间,所以p1下的引用计数为2,在p1的析构函数时对其引用计数减一,发现并没有为0,所以选择不释放p1的空间和p1的引用计数的空间。这样就造成了内存泄漏。我们称之为:循环引用   所以,弱指针weak_ptr就应运而生了,其实在库中shared_ptr和weak_ptr这两个智能指针类都公有继承了一个抽象的引用计数的类,所以,shared_ptr和weak_ptr的实现方式所差无几,就是二者的引用计数有区别。

    weak_ptr:   库中的shared_ptr和weak_ptr基本是这样实现的:

    pragma once #include <memory> #define _MT_INCR(mtx, x) _InterlockedIncrement(&x) #define _MT_DECR(mtx, x) _InterlockedDecrement(&x) #define _MT_CMPX(x, y, z) _InterlockedCompareExchange(&x, y, z) class RefCountBase { private: virtual void _Destroy() = 0; virtual void _DeleteThis() = 0; long _Uses; long _Weaks; protected: RefCountBase() : _Uses(1) , _Weaks(1) {} public: virtual ~RefCountBase() {} // 如果usecount不为0时,增加usecount的引用计数,成功返回true bool _Incref_nz() { // 循环知道增加成功 for (; ; ) { // loop until state is known long _Count = (volatile long&)_Uses; if (_Count == 0) return (false); if (_MT_CMPX(_Uses, _Count + 1, _Count) == _Count) return (true); } } // 增加usecount void _Incref() { _MT_INCR(_Mtx, _Uses); } // 增加weakcount引用计数 void _Incwref() { _MT_INCR(_Mtx, _Weaks); } // 减少usecount引用计数 void _Decref() { // 减少usecount引用计数,如果usecount减后为0,释放资源 if (_MT_DECR(_Mtx, _Uses) == 0) { //释放管理的资源,减少weakcount引用计数 _Destroy(); _Decwref(); } } // 减少weakcount的引用计数 void _Decwref() { if (_MT_DECR(_Mtx, _Weaks) == 0) _DeleteThis(); } // 获取usecount long _Use_count()const { return (_Uses); } bool _Expired() const { return (_Uses == 0); } virtual void *_Get_deleter(const _XSTD2 type_info&) const { return (0); } }; template<class T> class RefCount: public RefCountBase { public: RefCount(T* p) : RefCountBase() , _p(p) {} private: // 释放管理的资源 virtual void _Destroy() { delete _p; } // 释放自身 virtual void _DeleteThis() { delete this; } T * _p; }; template<class T,class Del> class RefCountDel: public RefCountBase { public: RefCountDel(T *p, Del Dtor) : RefCountBase() , _p(p) , _Dtor(Dtor) {} virtual void *_Get_deleter(const _XSTD2 type_info& _Type) const { // return address of deleter object return ((void *)(_Type == typeid(Del) ? &_Dtor : 0)); } private: // 释放管理的资源 virtual void _Destroy() { _Dtor(_p); } virtual void _DeleteThis() { delete this; } T * _p; Del _Dtor; }; //定制空间配置器 template<class T,class Del,class Alloc> class RefCountDelAlloc: public RefCountBase { public: typedef RefCountDelAlloc<T, Del, Alloc> _Myty; typedef typename Alloc::template rebind<_Myty>::other _Myalty; RefCountDelAlloc(T* p, Del Dtor, _Myalty _Al) : RefCountBase() , _p(p) , _Dtor(Dtor) , _Myal(_Al) {} virtual void *_Get_deleter(const _XSTD2 type_info& _Type) const { return ((void *)(_Type == typeid(Del) ? &_Dtor : 0)); } private: // 释放管理的资源 virtual void _Destroy() { _Dtor(_p); } // 释放自身 virtual void _DeleteThis() { // destroy self _Myalty _Al = _Myal; _Dest_val(_Al, this); _Al.deallocate(this, 1); } T * _p; Del _Dtor; // the stored destructor for the controlled object _Myalty _Myal; // the stored allocator for this }; // DECLARATIONS template<class T> class WeakPtr; template<class T> class SharedPtr; // SharedPtr和WeakPtr的基类 template<class T> class PtrBase { public: typedef PtrBase<T> _Myt; typedef T Elem; typedef Elem ElementType; PtrBase() : _ptr(0) , _pRef(0) {} PtrBase(_Myt&& _Right) : _ptr(0) , _pRef(0) { _Assign_rv(_STD forward<_Myt>(_Right)); } // construct _Ptr_base object that takes resource from _Right _Myt& operator=(_Myt&& _Right) { _Assign_rv(_STD forward<_Myt>(_Right)); return (*this); } // assign by moving _Right void _Assign_rv(_Myt&& _Right) { if (this != &_Right) _Swap(_Right); } // 获取引用计数 long UseCount() const { // return use count return (_pRef ? _pRef->_Use_count() : 0); } void _Swap(PtrBase& _Right) { std::swap(_pRef, _Right._pRef); std::swap(_ptr, _Right._ptr); } void* GetDeleter(const _XSTD2 type_info& _Type) const { return (_pRef ? _pRef->_Get_deleter(_Type) : 0); } T* _Get() const { return (_ptr); } bool Expired() const { return (!_pRef || _pRef->_Expired()); } // 增加引用计数usecount void _Decref() { if (_pRef != 0) _pRef->_Decref(); } void _Reset() { _Reset(0, 0); } template<class Ty> void _Reset(const PtrBase<Ty>& pb) { _Reset(pb._ptr, pb._pRef); } template<class Ty> void _Reset(Ty *Ptr, const PtrBase<Ty>& pb) { _Reset(Ptr, pb._pRef); } void _Reset(T* ptr, RefCountBase* pRef) { if (pRef) pRef->_Incref(); _Reset0(ptr, pRef); } void _Reset0(T* ptr, RefCountBase *pRef) { if (_pRef != 0) _pRef->_Decref(); _pRef = pRef; _ptr = ptr; } void _Decwref() { if (_pRef != 0) _pRef->_Decwref(); } void _Resetw() { _Resetw((_Elem *)0, 0); } template<class Ty> void _Resetw(const PtrBase<Ty>& pb) { _Resetw(pb._ptr, pb._pRef); } template<class Ty> void _Resetw(const Ty* ptr, RefCountBase* pRef) { _Resetw(const_cast<Ty*>(ptr), pRef); } template<class Ty> void _Resetw(Ty* ptr, RefCountBase* pRef) { if (pRef) pRef->_Incwref(); if (_pRef != 0) _pRef->_Decwref(); _pRef = pRef; _ptr = ptr; } private: T *_ptr; RefCountBase *_pRef; }; template<class T> class SharedPtr: public PtrBase<T> { public: typedef SharedPtr<T> _Myt; typedef PtrBase<T> _Mybase; SharedPtr() {} explicit SharedPtr(T* p) { _Resetp(p); } // 带有删除器的构造函数 template<class U,class Del> SharedPtr(U *p, Del Dt) { _Resetp(p, Dt); } // 带有空间配置器的构造函数 template<class U,class D,class Alloc> SharedPtr(U *p, D Dt, Alloc Ax) { _Resetp(p, Dt, Ax); } SharedPtr(const _Myt& _Other) { this->_Reset(_Other); } template<class Ty> explicit SharedPtr(const WeakPtr<Ty>& _Other,bool _Throw = true) { this->_Reset(_Other, _Throw); } template<class Ty> explicit SharedPtr(const WeakPtr<Ty>& _Other) { this->_Reset(_Other, _Throw); } ~SharedPtr() { this->_Decref(); } _Myt& operator=(const _Myt& _Right) { SharedPtr(_Right).Swap(*this); return (*this); } void Reset() { SharedPtr().swap(*this); } template<class U> void Reset(U* p) { SharedPtr(p).swap(*this); } template<class U,class Del> void Reset(U* p, Del Dt) { SharedPtr(p, Dt).swap(*this); } template<class U,class D,class _Alloc> void Reset(U *P, D Del, _Alloc _Ax) { SharedPtr(P, Del, _Ax).swap(*this); } void Swap(_Myt& sp) { this->_Swap(sp); } T* Get() const { return (this->_Get()); } T& operator*() const { return (*this->_Get()); } T* operator->() const { return (this->_Get()); } bool Unique() const { return (this->use_count() == 1); } private: template<class U> void _Resetp(U* p) { _Resetp0(p, new RefCount<U>(p)); } template<class U,class D> void _Resetp(U *p, D Del) { _Resetp0(p, new RefCountDel<U, D>(p, Del)); } public: template<class U> void _Resetp0(U* p, RefCountBase* pRef) { this->_Reset0(p, pRef); } }; template<class T> void Swap(SharedPtr<T>& _Left,SharedPtr<T>& _Right) { _Left.swap(_Right); } template<class D,class T> D* GetDeleter(const SharedPtr<T>& _Sx) { return ((D *)_Sx._Get_deleter(typeid(D))); } template<class T> class WeakPtr: public PtrBase<T> { public: WeakPtr() {} WeakPtr(const WeakPtr& wp) { this->_Resetw(wp); } ~WeakPtr() { this->_Decwref(); } WeakPtr& operator=(const WeakPtr& wp) { this->_Resetw(wp); return (*this); } WeakPtr& operator=(SharedPtr<T>& sp) { this->_Resetw(sp); return (*this); } void Reset() { this->_Resetw(); } void Swap(WeakPtr& _Other) { this->_Swap(_Other); } bool Expired() const { return (this->_Expired()); } };

      即:weak_ptr也维护了一个引用计数,跟shared_ptr维护的引用计数或互不干扰,或相互协同。weak_ptr的指针会在weak_ptr维护的引用计数上加一,而shared_ptr会在shared_ptr维护的引用计数上加一,这样在循环引用时,就会因为对不同引用的判断的不同,使最终决定是否释放空间的结果也不相同。具体方式在下面举例说明。   所以在解决上述循环引用的问题时,只需要定义下面这样的结构体和在main中这样管理一个节点就ok:   

    template<typename T> struct Node { public:   Node(const T& data) :_value(data) { }   ~Node()   {     //释放_nex&_prev   } private: weak_ptr<Node<T>> _next; weak_ptr<Node<T>> _prev; T _value; }; main中: int main() {   shared_ptr<Node<int>> p1(new Node<int>(1));   shared_ptr<Node<int>> p2(new Node<int>(2));   p1->next=p2;   p2->prev=p1;   return 0; }

      即结构体中使用两个弱指针weak_ptr管理它的next和prev域,而这个节点本身为shared_ptr。这种用法使得作用域完毕执行析构函数时按如下方式执行:   根据图解,只是发现在引用计数的地方多了一个计数,usecount为shared_ptr维护的引用计数,weakcount为weak_ptr维护的引用计数。   在每一个节点使用构造函数构造出来时,usecount和weakcount都为1,这时p1的next指向p2,p1的weakcount+1=2,同理,p2的weakcount+1=2。中间在经过一系列操作后,到了作用域的结尾,先执行p2的析构函数,因为p2这个节点本身为shared_ptr,所以就使用shared_ptr的析构函数,对其suecount-1=0,发现可以释放这块空间,随机执行delete操作,释放掉p2的next和prev域。在释放p2的原生态指针后,管理p1的指针除了p1本身就没有了(p2->prev也不存在了)。检测p1的引用计数空间是否可释放,对weakcount-1之后为1,不为0,无法释放。所以p1的weakcount-1=1。此时p1:usecount=1,weakcount=1;p2:usecount=0,weakcount=2;此时调用p2operator delete释放掉p2的原生态空间,此时,检测是否可以释放p2的引用计数空间,对管理p2的weakcount引用计数减一,判断是否为0,不为0,无法释放。此时,p2的原生态指针空间释放,但引用计数空间没有释放,p2的usecount=0,weakcoun=1。   开始执行p1的析构函数,调用p1的shared_ptr的析构函数,对p1的suecount-1=0,发现为0,可以释放,即执行p1这个节点的析构函数,释放掉p1的_next和_prev域,释放后p2->prev指向的p1因为释放,所以p2的weakcount-1=0,可以释放,此时才释放掉p2的引用计数空间。然后释放p1的原生态指针空间,此时p1:usecount=0,weakcount=1。因为此时p1已经释放,所以原p1指向的空间无效,对p1的weakcount减一后检测是否可以释放,发现为0,可以释放,此时释放掉p1的引用计数空间。至此,所有空间全部释放完毕。   注意,我在描述的时候使用了很多的通俗说法,但是你使用库中的智能指针时,会发现每一步都在调用函数完成。 而且使用这种方式,多个节点的双向链表也可以实现。

    转载请注明原文地址: https://ju.6miu.com/read-25.html

    最新回复(0)