C + + smart pointer shared_ptr

Posted by kinaski on Tue, 18 Jan 2022 11:52:36 +0100

Pointer

C + + provides the method of pointer operation. When we open up the space of the specified type with new, a pointer is generated.

void use_pointer()
{
    //An integer pointer to an element with a value of 5
    int *pint = new int(5);
    //A pointer to a string
    string *pstr = new string("hello zack");
}

The pointer object can be generated by new + type construction, but the space occupied by the opened pointer object is in the heap space. Manual recycling is required.
It can be recycled by deleting the pointer object

void use_pointer()
{
    //An integer pointer to an element with a value of 5
    int *pint = new int(5);
    //Pointer to string
    string *pstr = new string("hello zack");
    //Free the space pointed to by the pin
    if (pint != nullptr)
    {
        delete pint;
        pint = nullptr;
    }
    //Free the space pointed to by the pointer.
    if (pstr != nullptr)
    {
        delete pstr;
        pstr = nullptr;
    }
}

Reclaim the heap space pointed to by the delete pointer object. To prevent double free, set the released objects to nullptr respectively.
Pointers have many hidden dangers:
When a function returns a pointer to a local variable, external use of the pointer may cause a crash or logic error. Because the local variable is released with the right} release of the function.
2 if multiple pointers point to the same heap space, and one of them frees the heap space, the use of other pointers will cause a crash.
3. Deleting a pointer multiple times will cause the double free problem.
4. Two class objects A and B respectively contain pointer members of each other's types. How to release them when referring to each other is A problem.

Therefore, C + + proposes the use of smart pointer, which can solve the above hidden dangers.
shared_ptr allows multiple pointers to point to the same object;
unique_ptr "monopolizes" the object pointed to.
The standard library also defines a named weak_ The companion class of PTR, which is a weak reference and points to shared_ Objects managed by PTR.
All three types are defined in the memory header file.

    //We define a pointer to integer 5
    auto psint2 = make_shared<int>(5);
    //Determine whether the smart pointer is empty
    if (psint2 != nullptr)
    {
        cout << "psint2 is " << *psint2 << endl;
    }

    auto psstr2 = make_shared<string>("hello zack");
    if (psstr2 != nullptr && !psstr2->empty())
    {
        cout << "psstr2 is " << *psstr2 << endl;
    }

There is no difference between the use of smart pointer and ordinary built-in pointer. You can judge whether the pointer is null pointer by judging whether the pointer is nullptr.
Through - > you can get the member method or member variable inside the pointer.
make_ The shared function takes the parameter as the parameter of the constructor of the object type, and passes this parameter to the constructor of the object type in the template, so as to construct the intelligent pointer of the object type, which saves the cost of the object passing in the function.
When we need to get a built-in type, we can return its underlying built-in pointer through the smart pointer method get().

    int *pint = psint2.get();
    cout << "*pint  is " << *pint << endl;

Do not recycle the built-in pointer returned by the smart pointer get manually. Just give it to the smart pointer to recycle itself, otherwise it will cause double free or crash using the smart pointer.
Don't initialize another smart pointer with the built-in pointer returned by get(), because two smart pointers reference one built-in pointer, for example, if one releases the other, it will cause a crash.
shared_ptr will manage the built-in pointer according to the reference count. When the reference count is 0, the built-in pointer will be automatically deleted.
When a smart pointer p is assigned to another smart pointer q, the p reference count is - 1 and the q reference count is + 1

void use_sharedptr()
{
    //We define a pointer to integer 5
    auto psint2 = make_shared<int>(5);
    auto psstr2 = make_shared<string>("hello zack");
    //Assign psint2 to psint3, and their underlying built-in pointers are the same
    // The reference counts of psint3 and psint2 are the same. The reference count + 1 is 2
    shared_ptr<int> psint3 = psint2;
    //Print reference count
    cout << "psint2 usecount is " << psint2.use_count() << endl;
    cout << "psint3 usecount is " << psint3.use_count() << endl;
    // The psint3 reference count is 1
    psint3 = make_shared<int>(1024);
    // psint2 reference count - 1, becomes 1
    //Print reference count
    cout << "psint2 usecount is " << psint2.use_count() << endl;
    cout << "psint3 usecount is " << psint3.use_count() << endl;
}

Program output

psint2 usecount is 2
psint3 usecount is 2
psint2 usecount is 1
psint3 usecount is 1

You can use shared_ptr implements data sharing. We define a StrBlob class, which only has another member, shared_ptr member is used to manage vectors and record how many StrBlob class objects use vectors. When all strblobs are destroyed, vectors are automatically recycled.

class StrBlob
{
public:
    //Define type
    typedef std::vector<string>::size_type size_type;
    StrBlob();
    //Construct by initializing the list
    StrBlob(const initializer_list<string> &li);
    //Returns the vector size
    size_type size() const { return data->size(); }
    //Determine whether the vector is empty
    bool empty()
    {
        return data->empty();
    }
    //Write element to vector
    void push_back(const string &s)
    {
        data->push_back(s);
    }

    //Pop up elements from vector
    void pop_back();
    //Access header element
    std::string &front();
    //Access tail element
    std::string &back();

private:
    shared_ptr<vector<string>> data;
};

Because StrBlob does not overload the assignment operator and does not implement the copy constructor, the assignment between StrBlob objects is a shallow copy. Therefore, the internal member data will modify the reference count with the assignment of StrBlob objects. By default, when we copy, assign or destroy a StrBlob object, its shared_ptr members are copied, assigned, or destroyed.
Of course, we can also implement copy construction and assignment operations, so that we can better understand the sharing effect of smart pointers with class object assignment and other operations.
After operator overloading, in order to make the program more perfect, the complete class declaration of copy construction and operator overloading is given here.

class StrBlob
{
public:
    //Define type
    typedef std::vector<string>::size_type size_type;
    StrBlob();
    //Construct by initializing the list
    StrBlob(const initializer_list<string> &li);
    //copy constructor 
    StrBlob(const StrBlob &sb);
    StrBlob &operator=(const StrBlob &sb);

    //Returns the vector size
    size_type size() const { return data->size(); }
    //Determine whether the vector is empty
    bool empty()
    {
        return data->empty();
    }
    //Write element to vector
    void push_back(const string &s)
    {
        data->push_back(s);
    }

    //Pop up elements from vector
    void pop_back();
    //Access header element
    std::string &front();
    //Access tail element
    std::string &back();

private:
    shared_ptr<vector<string>> data;
    //Detect if i is out of bounds
    void check(size_type i, const string &msg) const;
};

Next, we implement three constructors

StrBlob::StrBlob()
{
    data = make_shared<vector<string>>();
}

StrBlob::StrBlob(const StrBlob &sb)
{
    data = sb.data;
}

StrBlob::StrBlob(const initializer_list<string> &li)
{
    data = make_shared<vector<string>>(li);
}

The default constructor initializes data to an empty vector. The copy constructor assigns sb's data to itself. The initializing list constructor constructs data from the initializing list. Next, overload the assignment operator

StrBlob &StrBlob::operator=(const StrBlob &sb)
{
    if (&sb != this)
    {
        this->data = sb.data;
    }

    return *this;
}

Assign the data of sb to this - > data, so that this - > data and sb The data reference count is the same.
We implement a function that checks for out of bounds

//Detect if i is out of bounds
void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
    {
        throw out_of_range(msg);
    }
}

Next, implement front

string &StrBlob::front()
{
    //Do not return a reference to a local variable
    // if (data->size() <= 0)
    // {
    //     return string("");
    // }
    // 1 can return an exception with a local variable
    if (data->size() <= 0)
    {
        return badvalue;
    }
    return data->front();
}

To consider the case where the queue is empty, an empty string is returned. However, if we directly construct an empty string to return, the reference of the local variable will be returned, and the local variable will be released with the end of the function, resulting in security risks. So we can return the member variable badvalue of the class as a token that the queue is empty. Of course, if the queue is empty and can be handled by throwing an exception, we rewrite the front in this way

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

Similarly, we implement back () and pop_back()

string &StrBlob::back()
{
    check(0, "back on empty StrBlog");
    return data->back();
}

void StrBlob::pop_back()
{
    check(0, "back on pop_back StrBlog");
    data->pop_back();
}

In this way, we can share the vector by defining the StrBlob class. The of multiple StrBlob operations is a vector.
We added a new print shared_ptr reference counting method

void StrBlob::printCount()
{
    cout << "shared_ptr use count is " << data.use_count() << endl;
}

Test the following

void test_StrBlob()
{
    StrBlob strblob1({"hello", "zack", "good luck"});
    StrBlob strblob2;
    try
    {
        auto str2front = strblob2.front();
    }
    catch (std::out_of_range &exc)
    {
        cout << exc.what() << endl;
    }
    catch (...)
    {
        cout << "unknown exception" << endl;
    }

    strblob2 = strblob1;
    auto str1front = strblob1.front();
    cout << "strblob1 front is " << str1front << endl;

    strblob2.printCount();
    strblob1.printCount();
}

Program output

front on empty StrBlob
strblob1 front is hello
shared_ptr use count is 2
shared_ptr use count is 2

Because the queue of strblob2 is empty, an exception will be thrown. When strblob2 = strblob1 is executed, the reference count of data of strblob2 and strblob1 is the same, and both are 2.

shared_ptr combined with new

The previous methods were through make_ Shared < type > (constructor list parameter)_ PTR, or share can be generated through the built-in pointer initialization generated by new_ ptr.

    auto psint = shared_ptr<int>(new int(5));
    auto psstr = shared_ptr<string>(new string("hello zack"));

The smart pointer constructor that accepts pointer parameters is explicit. Therefore, we cannot implicitly convert a built-in pointer to a smart pointer. We must initialize a smart pointer in the form of direct initialization:

    //Error, shared cannot be implicitly initialized with built-in pointer_ ptr
    // shared_ptr<int> psint2 = new int(5);
    //Correct, display initialization
    shared_ptr<string> psstr2(new string("good luck"));

In addition to the assignment between smart pointers, one smart pointer can be used to construct another

    shared_ptr<string> psstr2(new string("good luck"));
    //You can use a shared_ptr constructs another shared_ptr
    shared_ptr<string> psstr3(psstr2);
    cout << "psstr2 use count is " << psstr2.use_count() << endl;
    cout << "psstr3 use count is " << psstr3.use_count() << endl;

Program output

psstr2 use count is 2
psstr3 use count is 2

Construct another smart pointer through one pointer. The two pointers share the underlying built-in pointer, so the reference count is 2
While constructing smart pointers, you can specify a user-defined deletion method to replace shared_ptr's own delete operation

    //You can set a new delete function instead of delete
    shared_ptr<string> psstr4(new string("good luck for zack"), delfunc);

We have specified the delfunc delete function for psstr4, so that when psstr4 is released, the delfunc function will be executed instead of the delete operation.

void delfunc(string *p)
{
    if (p != nullptr)
    {
        delete (p);
        p = nullptr;
    }

    cout << "self delete" << endl;
}

We implemented our own delfunc function as a delector, recycled the built-in pointer, and printed the deletion information. In this way, "self delete" will be printed when psstr4 performs destruct.
Make is recommended_ Construct smart pointers in the way of shared.
If you generate smart pointers through built-in pointer initialization, remember not to recycle built-in pointers manually.
When a shared_ When PTR is bound to a common pointer, we will assign the responsibility of memory management to the shared_ptr.
Once this is done, we should no longer use built-in pointers to access shared_ The memory pointed to by PTR.
There is a problem with the following code

void process(shared_ptr<int> psint)
{
    cout << "psint data is " << *psint << endl;
}

int main()
{
    int *p = new int(5);
    process(shared_ptr<int>(p));
    //Danger, p has been released, will cause crash or logic error
    cout << "p data is " << *p << endl;
    return 0;
}

Program output

psint data is 5
p data is 10569024

Because P is constructed as shared_ptr, then its recycling is handed over to shared_ptr, and shared_ptr is the formal parameter of process. The formal parameter will be released at the end of process, so p will also be recycled. Then accessing P will cause a logic error, so an illegal memory value is printed.

The smart pointer type defines a function called get, which returns a built-in pointer to the object managed by the smart pointer.
This function is designed for a situation where we need to pass a built-in pointer to code that cannot use smart pointers.
Code that uses a pointer returned by get cannot delete this pointer.

void bad_use_sharedptr()
{
    shared_ptr<int> p(new int(5));
    //Get the built-in pointer q through p
    //Note that q is bound by p at this time. Do not delete q manually
    int *q = p.get();
    {
        //Two independent shared_ Both PTR m and p bind q
        auto m = shared_ptr<int>(q);
    }

    //When the above} ends, m is recycled and its bound q is also recycled
    //Using q at this time is illegal operation, crash or logic error
    cout << "q data is " << *q << endl;
}

Although the above code does not manually delete q, there are two independent shared_ptr m and p are bound to Q, which causes the memory of Q to be recycled when one of M is recycled, so there will be a crash or data exception when accessing * q later.
Note that the following code is different from the above. m and p now share q, and the reference count is shared and synchronized.

void good_use_sharedptr()
{
    shared_ptr<int> p(new int(5));
    //Get the built-in pointer q through p
    //Note that q is bound by p at this time. Do not delete q manually
    int *q = p.get();
    {
        // Both m and p have reference counts of 2
        shared_ptr<int> m(p);
    }

    //When the above} ends, m is recycled and its bound q is also recycled
    //Using q at this time is illegal operation, crash or logic error
    cout << "q data is " << *q << endl;
}

Therefore, it is summarized as follows:
Get is used to pass the access permission of the pointer to the code. You can use get only when you are sure that the code will not delete the pointer.
In particular, never initialize another smart pointer with get or assign a value to another smart pointer.

reset

The function of reset is shared_ptr reopens a new piece of memory for shared_ptr binding memory

    shared_ptr<int> p(new int(5));
    // p rebind the new built-in pointer
    p.reset(new int(6));

The above code rebind the new memory space for p.
The common case of reset is to judge whether the smart pointer monopolizes memory. If the reference count is 1, that is, it monopolizes memory, modify it. Otherwise, bind a new memory for the smart pointer to modify it, so as to prevent multiple smart pointers from sharing a piece of memory. Modifying memory by one smart pointer will affect other smart pointers.

    //unique returns true if the reference count is 1
    if (!p.unique())
    {
        //There are other references, so we point to new memory for p
        p.reset(new int(6));
    }

    // p is currently the only user
    *p = 1024;

Another advantage of using smart pointers is that when a program crashes, smart pointers can also ensure that memory space is recycled

void execption_shared()
{
    shared_ptr<string> p(new string("hello zack"));
    //Exception caused here
    int m = 5 / 0;
    //Even a crash will ensure that p is recycled
}

Even if it runs to m = 5 / 0, the program crashes and the smart pointer p will be recycled.
Sometimes when we pass a smart pointer, the pointer is not allocated by new, so we need to pass a delegator to it ourselves

void delfuncint(int *p)
{
    cout << *p << " in del func" << endl;
}

void delfunc_shared()
{
    int p = 6;
    shared_ptr<int> psh(&p, delfuncint);
}

If you do not pass delfuncint, p will be deleted by the smart pointer. Because p is a variable in stack space, using delete will cause a crash.

summary

Smart pointer trap smart pointer can provide safe and convenient management of dynamically allocated memory, but it is based on correct use.
In order to correctly use smart pointers, we must adhere to some basic specifications:
·Multiple smart pointers are not initialized (or reset) with the same built-in pointer value.
·Do not delete the pointer returned by get().
·Do not initialize or reset another smart pointer using get().
·If you use the pointer returned by get (), remember that when the last corresponding smart pointer is destroyed, your pointer becomes invalid.
·If the resource you use the smart pointer to manage is not the memory allocated by new, remember to pass it a remover.

Source code connection
https://gitee.com/secondtonone1/cpplearn

Topics: C++