Brief Analysis of Intelligent Pointer

When it comes to smart pointers, you must first know RAII, because smart pointers are an application of RAII. RAII is essentially a resource allocation that is initialization. Define a class to encapsulate the allocation and release of resources, allocate and initialize resources in the constructor, clean up resources in the destructor, and guarantee capital.Proper initialization and release of the source.

So why do we use smart pointers?Didn't we learn the dynamic memory management identifiers new and delete for allocating and cleaning resources?That's enough. Why use smart pointers? Because we are trying to prevent unexpected events that may result in not freeing up space in time, which can lead to memory leaks and program hanging up.Most often in programs with exceptions,
as follows

test()
{
    int* p = new int;
    dosomething();....//This function has an exception;
    delete p; 
}

The dosomething() function has an exception; a jump occurs, interrupting the execution stream so that delete p is not executed; therefore, this can easily cause a memory leak.Of course, programmatic apes can free up space by catching exceptions to handle them.But in this way, the apes have always had to consider the anomalies to increase their workload, and they are still manual (whether there are very people to test, in case you forget), and write ugly code, so do we have a good way to manage space?For example, do not release space manually, let it automatically release space?
Of course, the answer is yes.That is our smart pointer, which divides our native pointer into an object to manage space. When an object is out of scope, it automatically calls a destructor to free up space.
Is it great, and don't worry about forgetting to free up space (because it's automatically released, oh)
Next we'll implement the first smart pointer (the earliest smart pointer, version c++ 98 ~03) without saying anything, just code <implemented with template class>

//Auto_ptr Implemented by c++ Standard Library
#include <iostream>
using namespace std;

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

    ~Auto_ptr()
    {
        if (_ptr != NULL)
        {
            delete _ptr;
        }
        _ptr = NULL;
    }
    //s2(s1); transfer of management power
    Auto_ptr(Auto_ptr<T>& s)
        :_ptr(s._ptr)
    {       
        s._ptr = NULL;
    }

    Auto_ptr<T>& operator= (Auto_ptr<T>& s)
    {
        if (this != &s)
        {
            delete _ptr;// Prevent memory leaks
            _ptr = s._ptr;
            s._ptr = NULL;
        }
        return *this;
    }

    T& operator*()
    {
        if (_ptr != NULL)
        {
            return *_ptr;
        }
    }

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

private:
    T* _ptr;
};

void test()
{
    int *p = new int(10);
    Auto_Ptr<int> ap1<p>;

}
int main()
{
    test();
    return 0;
}

(1) Copy the constructor first. Can we use the default constructor instead?Obviously not, because the default constructor is a copy of the value, pointing two pointers to the same block of space.It destructs twice.So is it possible to use a deep copy, which is obviously not possible, because two pointers point to different spaces, how do you manage them with one pointer?
Our great designers have invented a way to solve this problem by transferring management power.See the picture in detail

You can see that it does, but it leaves s._ptr empty and what to do in case the pointer is still used in the future, so it is defective;
(2) Similarly, look at the overload of assignment operators and write out the corresponding code.

(3) Smart pointers have to access objects like our native pointers, so our overloads * and -> Note that when overloading * the return value must be T& because it returns a reference (alias) to a temporary object (copied constructed), indicating that the scope object exists and can be modified.If you do not add &, a temporary object is returned, and the temporary object is constant, so its value cannot be modified, and it cannot be used as a value anymore.
(4), overload->, ap2->a1 = 30;
Calling ap2.operator->() returns _ptr, _ptr->a1 = 30; the compiler handles ->optimization, which should be understood.
(5), destructor.When an exception occurs, out of scope, the destructor is called to free up space without worrying about memory not being freed after interrupting the program's execution stream.
(6) The constructor is to pass in a pointer.

Okay, that's the basic point of Auto_Ptr, but it's flawed, so try not to use it,
As development progresses, a new smart pointer has emerged, which has been developed in the tripartite library boost. It is called ScopedPtr, also known as anti-copy.Its core is to inherit Autoptr, but change operator = and copy construction to declare only, undefined, and protected; this prevents others from defining it.
Next, look at the code

boost Library implementation scoped_ptr,scoped_Array
template <typename T>
class Scoped_ptr
{
public:
    Scoped_ptr(T* ptr)
        :_ptr(ptr)
    {}
    ~Scoped_ptr()
    {
        if (_ptr != NULL)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
    T* operator->()
    {
        return _ptr;
    }
    T& operator*()
    {
        return *_ptr;
    }

protected:
    Scoped_ptr(Scoped_ptr<T>& s);//Declare only that it is not implemented and that it is private, preventing others from implementing and invoking it
    Scoped_ptr<T>& operator=(Scoped_ptr<T>& s);

private:
    T* _ptr;
};

template <typename T>
class ScopedArray
{
public:
    ScopedArray(T* ptr)
        :_ptr(ptr)
    {}
    ~ScopedArray()
    {
        if (_ptr != NULL)
        {
            delete[] _ptr;
            _ptr = NULL;
        }
    }
    T& operator[](int index)
    {
        return _ptr[index];
    }
protected:
    ScopedArray(ScopedArray<T>& sarr);
    ScopedArray& operator=(ScopedArray<T>& sarr);
private:
    T* _ptr;

};

The other is that the boost library also has a smart pointer to manage arrays, and nothing at all.

With the advent of c++11, a new smart pointer appears, which is SharedPtr, which is based on reference counting. It is powerful. Let's look at its implementation first, and then explain it. It is also implemented using template classes.

//C++ Standard Library Implements SharedPtr
#include <iostream>
using namespace std;

template <typename T>
class SharedPtr
{
public:
    SharedPtr(ptr)
        :_ptr(ptr)
        , _refcount(new int(1));
    {}

    ~SharedPtr()
    {
        realse();
    }
    void realse()
    {
        if (--*(_refcount) == 0)
        {
            delete _ptr;
            delete _refcount;
        }
    }
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }

    SharedPtr(const SharedPtr<T>& sp)
        :_ptr(sp._ptr)
        , _refcount(sp._refcount)
    {
        (*_refcount)++;
    }

    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (_ptr != sp._ptr)
        {
            this->realse();
            _ptr = sp._ptr;
            _refcount = sp._refcount;
            (*_refcount)++;
        }
        return *this;
    }

private:e
    T* _ptr;
    int* _refcount;
};

1. Data members,

First, let's look at its data members, which are two pointers, pointing to different spaces.Maybe you don't look bad either.But if you want to know why the second data member should use int* _refcount instead of int _refcount or static _refcount, you're too good. At least I have to think of you and I learned it the other day. Let me talk about my understanding.
_Use int_refcout
So that each smart pointer object has a reference count,
For example, as shown in the diagram,

These three reference counts are all 3, when the S1 pointer does not point to this space, the reference count minus 1, becomes 2, the other object reference count does not change, or is 3, when the s2 pointer does not point to this space, the reference count also decreases 1, becomes 2, they will not affect, and so does the s 3, also becomes 2,This is the space that should be destructed, but when the last pointer does not point to this space, it has a reference count of 2, so system detection does not destruct and memory leaks occur.

Second, use static_refcout to share a reference count for all objects, is that possible? The answer is no, let's see the diagram

Because it is shared for static s, they share a - refcount. When S4 and S5 point to another space, - refcount is added to 5, and the space is not released, so it's not possible.
III. Use int* _refcount to have each _refcount manage its own space.
As the smart pointer object increases, -refcount adds one, decreases, subtracts one, which gives you a good control over space release, let's see

That's basically how it works, let's look at member functions

Two, member functions

_Constructor, complete resource allocation and initialization, nothing to say
Second, destructors, when the reference count bit 0, nothing is said to free up space, the code will see at a glance
Iii, opreator* and operator-> are the same as above
_Copy construction, let's look at the diagram

Seeing the code from the diagram will naturally make sense
ⅴ.
operator=, follow by the diagram

We notice that we can't assign to ourselves, that objects pointing to the same space can't, or that normally, we can just look at the code.
Speaking of this, you can see that Sharedptr is really powerful, but it has a circular reference problem.So what is a circular reference, so let's take a look at a piece of code

struct Node  
{  
    int _data;  
    shared_ptr<Node> _next;  
    shared_ptr<Node> _prev;  
};  
void test()  
{  
    shared_ptr<Node> sp1(new Node);  
    shared_ptr<Node> sp2(new Node);  
    cout << sp1.use_count() << endl;  
    cout << sp2.use_count() << endl;  
    sp1->_next = sp2;  
    sp2->_prev = sp1;  
    cout << sp1.use_count() << endl;  
    cout << sp2.use_count() << endl;  
}  

The program above will eventually output 112. When the program ends, SP 1 and sp2 do not
Will be destructed (reference count is not changed to zero), what we want is space to be released, but it is not released, why?Let's look at the picture

So what can we do about this? We've introduced a weak_ptr weak pointer, which is used to assist shared_ptr and can't be used alone.Binding a weak_ptr to a shared_ptr does not change the reference count of shared_ptr, once the last
The shared_ptr pointing to the object is destroyed, and the object is destroyed, even if there is a weak_ptr pointing to the object, the object is released.Therefore, changing the Node structure defined above to the one defined below will solve the problem of circular references.

struct Node  
{  
    int _data;  
    weak_ptr<Node> _next;  
    weak_ptr<Node> _prev;  
};  

2. Custom Deletors
Intelligent pointers were introduced to resolve memory leaks caused by changes in program execution streams.Open and initialize in constructors, and clean up in destructors.Smart pointers are templates, so they should be resolvable
Various types of memory leak problems.Files opened in the constructor, closed in the destructor, space opened by malloc in the constructor, and free d in the destructor.Therefore, we must write out the corresponding memory release functions for each type.This is achieved by using a pseudo-function (overload ()).The following shows a simulated implementation of a custom deletor:

#include<iostream>  
using namespace std;  
#include<boost/shared_ptr.hpp>  

template<typename T,typename D = DefaultDelete>  
class SharedPtr  
{  
public:  
    SharedPtr(T* ptr)  
        :_ptr(ptr)  
        ,_pCount(new int (1))  
        ,_del(D())  
    {}  
    ~SharedPtr()  
    {  
        if (--*_pCount == 0)  
        {  
            //delete _ptr;  
            _del(_ptr);  
            delete _pCount;  
        }  
    }  
    SharedPtr(const SharedPtr<T,D>& sp)  
    {  
        _ptr = sp._ptr;  
        _pCount = sp._pCount;  
        ++*_pCount;  
    }  
    SharedPtr<T,D>& operator=(const SharedPtr<T,D>& sp)  
    {  
        if (_ptr != sp._ptr)  
        {  
            if (_ptr != nullptr)  
            {  
                if (--*_pCount == 0)  
                {  
                    //delete _ptr;  
                    _del(_ptr);  
                    delete _pCount;  
                }  
            }  
            else  
            {  
                _ptr = sp._ptr;  
                _pCount = sp._pCount;  
                ++(*_pCount);  
            }  
        }  
        return *this;  
    }  
private:  
    T* _ptr;  
    int* _pCount;  
    D _del;  
};  
//Default Deleter  
struct DefaultDelete  
{  
    void operator() (void* ptr)  
    {  
        delete ptr;  
    }  
};  
struct Fclose  
{  
    void operator() (void* ptr)  
    {  
        fclose((FILE* )ptr);  
    }  
};  
struct Free  
{  
    void operator() (void* ptr)  
    {  
        free(ptr);  
    }  
};  
void test_del()  
{  
    SharedPtr<int>sp1(new int (1));  
    SharedPtr<FILE,Fclose>sp2(fopen("hello.txt","w"));  
}  

When a smart pointer manages a file, the function to close the file is called when it is out of scope.This is called customization
Deletor.

Posted by sanchez77 on Thu, 04 Jul 2019 09:03:36 -0700