The history of smart pointers

1. The development history of
smart pointers starts from RAII. RAII requires that the validity period of resources is strictly bound to the life cycle of the object holding the resource, that is, the allocation (acquisition) of resources is completed by the constructor of the object. At the same time, the release of resources is completed by the destructor. Under this requirement, as long as the object can be destructed correctly, there will be no resource leakage problem.
(1) The design of auto _Ptr and auto_Ptr proposed in C++98 is to let two pointers point to the same space, but this will cause the problem of improper release and program crash.
(2) Three types of only pointers, Scoped_Ptr, Shared_Ptr, and Weak_Ptr, are designed in the Boost library.
(3) C++11 has been further improved to design three types of smart pointers, Unique_ptr, shared_ptr, and Weak_ptr. Unique_ptr is similar.
2. What are the design ideas, flaws of auto_Ptr/Scoped_Ptr/Shared_ptr/Weak_Ptr, and their respective analog implementations?
(1) Simple implementation of
auto_Ptr A few notes about auto_Ptr:
auto_Ptr cannot share ownership
auto_Ptr cannot point to an array
auto_Ptr cannot be used as a member of a container auto_Ptr cannot be initialized
through assignment operations

template<class T>
class auto_Ptr
{
public:
    auto_Ptr(T* ptr)
        :_ptr(ptr)
    {}

    // ap2(ap1)
    auto_Ptr(auto_Ptr<T>& ap)
        :_ptr(ap._ptr)
    {
        ap._ptr = NULL;
    }

    // ap1 = ap2
    auto_Ptr<T>& operator=(auto_Ptr<T>& ap)
    {
        if (this != &ap)
        {
            if (_ptr)
                delete _ptr;

            _ptr = ap._ptr;
            ap._ptr = NULL;
        }

        return *this;
    }

    ~auto_Ptr()
    {
        if (_ptr)
        {   
            delete _ptr;
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

protected:
    T* _ptr;
    };

It can be seen that there is an obvious flaw. When the constructor and copy constructor are used, the original smart pointer is invalid, and the right to use is handed over to the new smart pointer. This method is called management right transfer. An object points to that area. When there are two pointers pointing to the same area, the program will crash
write picture description here
(2) Boost stage Boost stage proposes Scoped_Ptr to guard the pointer against copying

The proposal of SharedPtr is mainly based on AutoPtr. In order to prevent assignment and copy construction, it can be solved in two ways: one: only declare without implementation;
second : declare as private, so that no error will be reported when compiling.
Mock implementation of Scoped_Ptr

template<class T>
class Scoped_Ptr
{
public:
    Scoped_Ptr(T* ptr)
        :_ptr(ptr)
    {}

    ~Scoped_Ptr()
    {
        delete _ptr;
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }
private:
    Scoped_Ptr(const Scoped_Ptr<T>&);
    Scoped_Ptr<T>& operator=(const Scoped_Ptr<T>&);

protected:
    T* _ptr;
};

Simple implementation of Shared_Ptr
Compared with AutoPtr, Shared _Ptr solves the problem of shared ownership of pointers. Shared_Ptr introduces reference counting, which can be freely copied and assigned.

template<class T, class Del>
class Shared_Ptr
{
public:
    Shared_Ptr()
        :_ptr(NULL)
        ,_refCount(NULL)
    {}

    Shared_Ptr(T* ptr = NULL, Del del)
        :_ptr(ptr)
        ,_refCount(new int(1))
        ,_del(del)
    {}

    ~Shared_Ptr()
    {
        if (--(*_refCount) == 0)
        {
            cout<<"delete"<<endl;
            delete _ptr;
            _del(_ptr);

            delete _refCount;
        }
    }

    Shared_Ptr(const Shared_Ptr<T, Del>& sp)
        :_ptr(sp._ptr)
        ,_refCount(sp._refCount)
    {
        ++(*_refCount);
    }

     sp2 = sp3
    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
    {
        if (this != &s)
        if(_ptr != sp._ptr)
        {
            if (--(*_refCount) == 0)
            {
                delete _ptr;
                delete _refCount;
            }

            _ptr = sp._ptr;
            _refCount = sp._refCount;
            ++(*_refCount);
        }

        return *this;
    }


    int RefCount()
    {
        return *_refCount;
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

    T* GetPtr() const
    {
        return _ptr;
    }

protected:
    T* _ptr;
    int* _refCount;

    Del _del;
};

Shared_ptr will bring circular reference problem. Weakptr is used to solve the problem of circular reference of Shared_ptr. WeakPtr is a weak pointer. The purpose of weak_ptr is to introduce a smart pointer for shared_ptr to assist shared_ptr work. It can only be constructed from a shared_ptr or another weak_ptr object. , its construction and destruction do not cause the reference count to be incremented or decremented.
Mock implementation of WeakPtr
  

template<class T>
class Weak_Ptr
{
public:
    Weak_Ptr()
        :_ptr(NULL)
    {}

    Weak_Ptr(const Shared_Ptr<T>& sp)
        :_ptr(sp.GetPtr())
    {}

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

protected:
    T* _ptr;
};

Although circular references can be effectively dereferenced through weak reference pointers, this method must be used only when the programmer can foresee circular references. It can also be said that this is only a compile-time solution. If
a , it will still cause a memory leak.
Circular reference problem
You have me, and I have you. In the end, the double-release cannot be released, which eventually leads to memory leaks. Take the double-linked list as an example.

struct ListNode
{
    Weak_Ptr<ListNode> _prev;
    Weak_Ptr<ListNode> _next;

    ~ListNode()
    {
        cout<<"~ListNode()"<<endl;
    }

};

void TestCycleRef()
{
    Shared_Ptr<ListNode> cur = new ListNode;
    Shared_Ptr<ListNode> next = new ListNode;

    cur->_next = next;
    next->_prev = cur;
}

write picture description here
Here, _next and _prev are both member variables. If the first space is to be released, it must be released after the second space is released. However, since the first space has a node pointing to the second space, only the first space can be released. After the block space is released, the second block of space can be released, which brings about the problem of circular reference.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326521057&siteId=291194637
Recommended