The usage and principle of four smart pointers

[]There are four types of [smart pointers , namely auto_ptr, scoped_ptr, shared_ptr, and weak_ptr in the boost library.]
  The role of a smart pointer is to manage a pointer, because there is the following situation: the allocated space is forgotten to be released at the end of the function, resulting in a memory leak. So the principle of the smart pointer is to automatically release the memory space at the end of the function, and there is no need to manually release the memory space.
  Method of operation: In the [constructor] , pass in the pointer that needs to be managed outside the class, you can give an empty default value, and then overload the symbols such as “->” “*” “=”… and then in the destructor When the pointer space is released, the intelligent management of the pointer is formed.

auto_ptr:

transfer resources. The principle and operation method are as mentioned above, but there is such a situation:
int a=10;
int *p=&a;
auto_ptr p1(p);
auto_ptr p2(p1);
  At this time, the smart pointer p1 cannot manage the p piece There is no memory space, because it is implemented in the copy constructor of auto_ptr:
auto_ptr(auto_ptr& p)
:_ptr(p._ptr)
{
  _ptr=NULL;
}
  In this case, the original smart pointer p1 can no longer be matched p operation, and at the end of the final function, when the destructor is executed, p1 will also be destructed. Even if p1 saves a null pointer at this time, the overhead caused by the release is unnecessary. And: auto_ptr has the following defects:
1> Do not use auto_ptr to save a pointer that is not dynamically opened up space, because when the scope ends, the destructor of the smart pointer will be executed to release this space, but the non-dynamic space cannot be released;

//correct case: 
int i= new  int ( 1 );    //space on heap - dynamically open 
auto_ptr< int > ap1 ( &i ) ;
 //error case: 
int i= 1 ;   //space on stack 
auot_ptr < int > ap2 ( &i ) ;

2> Do not use two auto_ptr pointers to point to the same pointer, the specific reasons explained above;
3> Do not use auto_ptr to point to an array of pointers, because the destructor of auto_ptr uses delete instead of delete[], which does not match;
4> Do not store auto_ptr in the container, because the original pointer cannot be used after assignment and copy construction.
  And most importantly, don’t use auto_ptr smart pointers under any circumstances.

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;
};

You can see that the biggest difference is that scoped_ptr does not give the definition of the overloaded operator of copy construction and assignment operator, but only the declaration under private, which means that scoped_ptr smart pointer cannot use one object to create another object, nor can it use assignment form. This undoubtedly improves the security of smart pointers, but there are no operations such as “++” and “–”, and of course, there are two more operations “*” and “->”. So this form of leaf is not the most perfect. So there is shared_ptr again.

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;  
};

The biggest difference between shared_ptr and the above two is that it maintains a reference count to detect whether the pointer managed by the current object is also used by other smart pointers (must be a smart pointer managed by shared_ptr), and it is referenced in the destructor function. Decrement the count by one to determine whether it is 0. If it is 0, release the space of the pointer and the reference count. In fact, this principle is the same as the shallow copy of the string class.
  This type of smart pointer makes our management very convenient and worry-free to a certain extent. The value stored in this space can be modified together with other smart pointers that jointly manage this space to achieve a “smart” effect.
  So is everything perfect? The answer is: No! Because there is still such a situation: when each pointer managed is a pointer to a doubly linked list, then our destructor has a big problem at this time.
  Now we assume a simplest case, there are only two nodes in this doubly linked list, and the prev of p1 and the next of p2 both point to null. But note that at this time, p1 itself manages a space, and the prev of p2 also manages the space managed by p1, so the reference count under p1 is 2, and its reference count is decremented by one when p1’s destructor is used. 0, so choose not to free p1’s space and p1’s reference-counted space. This creates a memory leak. We call it: circular reference
  Therefore, the weak pointer weak_ptr came into being. In fact, the two smart pointer classes shared_ptr and weak_ptr in the library share an abstract reference counting class. Therefore, the implementation methods of shared_ptr and weak_ptr are almost the same, that is, two There is a difference in the reference count of the user.

weak_ptr:
  The shared_ptr and weak_ptr in the library are basically implemented like this:

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()
      {}
      // If usecount is not 0, increase the reference count of usecount and return true successfully 
      bool _Incref_nz()
      {
            // loop until increment success 
            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);
            }
      }
      // increment usecount 
      void _Incref()
      {
            _MT_INCR(_Mtx, _Uses);
      }
      // Increase the weakcount reference count 
      void _Incwref()
      {
            _MT_INCR(_Mtx, _Weaks);
      }
      // Decrease usecount reference count 
      void _Decref()
      {      // Decrease usecount reference count, if usecount is 0, release resources 
            if (_MT_DECR(_Mtx, _Uses) == 0 )
            {
                  //Release managed resources and reduce the weakcount reference count
                  _Destroy();
                  _December();
            }
      }
      // Decrease the reference count of weakcount 
      void _Decwref()
      {
            if (_MT_DECR(_Mtx, _Weaks) == 0)
                  _DeleteThis();
      }
      // get 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 :
       // Release managed resources 
      virtual  void _Destroy()
      {
            delete _p;
      }
      // Release itself 
      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 :
       // Release managed resources 
      virtual  void _Destroy()
      {
            _Dtor(_p);
      }
      virtual void _DeleteThis()
      {
            delete this;
      }
      T * _p;
      Del _Dtor;
};
//Custom space configurator 
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 :
       // Release managed resources 
      virtual  void _Destroy()
      {
            _Dtor(_p);
      }
      // Release itself 
      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;
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);
      }
      // get reference count 
      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());
      }
      // increment the reference count 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((_Element *) 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);
      }
      // constructor with deleter 
      template < class  U , class  Del >
       SharedPtr ( U * p , Del  Dt )
      {
            _Resetp(p, Dt);
      }
      // Constructor with space configurator 
      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());
      }
};

That is: weak_ptr also maintains a reference count, and the reference count maintained by shared_ptr either does not interfere with each other, or cooperates with each other. The pointer of weak_ptr will add one to the reference count maintained by weak_ptr, and the shared_ptr will add one to the reference count maintained by shared_ptr, so that in the case of circular reference, the final decision on whether to release the space will be made due to the difference in the judgment of different references. The results are not the same. The specific method is illustrated in the following example.
  So when solving the above-mentioned circular reference problem, you only need to define the following structure and manage a node in main like this:

template<typename T>
struct Node
{
public:
  Node(const T& data)
                :_value(data)
       { }
  ~Node()
  {
    //release _nex&_prev
  }
 private:
      weak_ptr<Node<T>> _next;
      weak_ptr<Node<T>> _prev;
      T _value;
};

In 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;
}

That is, the structure uses two weak pointers weak_ptr to manage its next and prev fields, and the node itself is shared_ptr. This usage causes the destructor to execute when the scope finishes executing as follows:
  According to the diagram, it is only found that there is an additional count in the place of reference count, usecount is the reference count maintained by shared_ptr, and weakcount is the reference count maintained by weak_ptr.
  When each node is constructed using the constructor, usecount and weakcount are both 1, then the next of p1 points to p2, weakcount+1=2 of p1, and similarly, weakcount+1=2 of p2. After a series of operations in the middle, at the end of the scope, the destructor of p2 is executed first. Because the node of p2 itself is shared_ptr, the destructor of shared_ptr is used, and its suecount-1=0, it is found that it can be released In this space, the delete operation is randomly performed to release the next and prev fields of p2. After releasing the original ecological pointer of p2, the pointer to manage p1 is gone except p1 itself (p2->prev also does not exist). Check whether the reference count space of p1 can be released. After weakcount-1, it is 1, not 0, and cannot be released. So weakcount-1=1 of p1. At this time, p1: usecount=1, weakcount=1; p2: usecount=0, weakcount=2; at this time, call p2operator delete to release the original ecological space of p2. At this time, it is detected whether the reference counting space of p2 can be released, and the management The weakcount reference count of p2 is decremented by one to determine whether it is 0 or not, and it cannot be released. At this time, the original ecological pointer space of p2 is released, but the reference count space is not released, and the usecount=0 and weakcoun=1 of p2.
  Start to execute the destructor of p1, call the destructor of shared_ptr of p1, and find that the suecount-1=0 of p1 is 0, which can be released, that is, execute the destructor of the node of p1, and release the _next and In the _prev field, the p1 pointed to by p2->prev is released after the release, so the weakcount-1=0 of p2 can be released, and the reference count space of p2 is released only at this time. Then release the original ecological pointer space of p1. At this time, p1: usecount=0, weakcount=1. Because p1 has been released at this time, the space pointed to by p1 is invalid. After reducing the weakcount of p1 by one, check whether it can be released. If it is found to be 0, it can be released. At this time, the reference count space of p1 is released. So far, all the space has been released.
  Note that I use a lot of colloquialism when describing, but when you use the smart pointers in the library, you will find that each step is completed by calling a function. And in this way, a doubly linked list of multiple nodes can also be implemented.

Leave a Comment

Your email address will not be published. Required fields are marked *