The use of C++ smart pointers: the use of shared_ptr, weak_ptr, unique_ptr, use case description.

Directory of series articles

Contents of this chapter:
(1) Introduction to shared_ptr, weak_ptr, unique_ptr
(2) Memory leak caused by using share_ptr alone
(3) Combined use of shared_ptr and weak_ptr

Article directory

  • Table of Contents of Series Articles
  • Preface
  • 1. Use of shared_ptr, weak_ptr, unique_ptr
    • 1.1 shared_ptr
    • 1.2 weak_ptr
      • 1. Creation of weak_ptr pointer
      • 2. Member methods provided by weak_ptr template class
    • 1.3 unique_ptr
  • 2. Usage steps
    • 1.shared_ptr separate use case
    • 2.Use cases of weak_ptr and shared_ptr combined
  • Summarize

Foreword

C/C++ does not have a garbage collector designed to implement fine-grained operations on memory. Therefore, when writing projects in C/C++, developers need to pay special attention to the application and release of memory. This article introduces the use of shared_ptr and weak_ptr, analyzes their methods of detecting memory leaks, and points out their shortcomings. I hope readers can avoid memory leaks by improving pointer arithmetic or share_ptr, and try not to write code structures that even the detection algorithm cannot handle.

1. Use of shared_ptr, weak_ptr, unique_ptr

1.1 shared_ptr

share_ptr is a newly added smart pointer in C++11, and the resources it limits can be shared by multiple pointers.

Only pointers to dynamically allocated objects can be given to shared_ptr objects for hosting. If pointers to ordinary local variables and global variables are handed over to shared_ptr for hosting, there will be no problem when compiling, but an error will occur when the program is running, because a pointer that does not point to a dynamically allocated memory space cannot be destructed.

1.2 weak_ptr

C++11 weak_ptr smart pointers are the same as shared_ptr and unique_ptr type pointers. weak_ptr smart pointers are also implemented in the form of template classes. weak_ptr (T is the type of data pointed to by the pointer) is defined in the header file and is located in the std namespace. Therefore, to use weak_ptr type pointers, the program should first contain the following 2 statements:

#include <memory>
using namespace std;
// The second sentence is not necessary and does not need to be added. Then when using the unique_ptr pointer later, std:: must be marked.

It should be noted that although the C++11 standard positions weak_ptr as a type of smart pointer, this type of pointer is usually not used alone (has no practical use) and can only be used in conjunction with shared_ptr type pointers. Even, we can regard the weak_ptr type pointer as an auxiliary tool for the shared_ptr pointer. With the help of the weak_ptr type pointer, we can obtain some status information of the shared_ptr pointer, such as how many pointers point to the same shared_ptr pointer and whether the heap memory pointed to by the shared_ptr pointer is Has been released and so on.

It should be noted that when the pointer of the weak_ptr type pointer is the same as a shared_ptr pointer, the weak_ptr pointer will not increase the reference count of the pointed heap memory by 1; similarly, when the weak_ptr pointer is released, the reference count of the previously pointed heap memory will be The reference count will not be decremented by 1. In other words, the weak_ptr type pointer does not affect the reference count of the pointed heap memory space.

In addition, there are no overloaded * and -> operators in the weak_ptr template class, which means that the weak_ptr type pointer can only access the pointed heap memory but cannot modify it.

1. Creation of weak_ptr pointer

There are three ways to create a weak_ptr pointer:

  1. It is possible to create an empty weak_ptr pointer, for example:
std::weak_ptr<int> wp1;
  1. With an existing weak_ptr pointer, you can create a new weak_ptr pointer, for example:
std::weak_ptr<int> wp2 (wp1);

If wp1 is a null pointer, then wp2 is also a null pointer; conversely, if wp1 points to the heap memory owned by a shared_ptr pointer, then wp2 also points to the block storage space (can be accessed, but has no ownership).

  1. The weak_ptr pointer is more commonly used to point to the heap memory owned by a shared_ptr pointer, because when constructing the weak_ptr pointer object, the existing shared_ptr pointer can be used to initialize it. For example:
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);

Thus, the wp3 pointer and the sp pointer have the same pointer. Again, weak_ptr type pointers do not cause the reference count of the heap memory space to increase or decrease.

2. Member methods provided by weak_ptr template class

Compared with shared_ptr and unique_ptr, the weak_ptr template class does not provide many member methods.

weak_ptr pointer callable member method

operator=() Overloaded = assignment operator, yes weak_ptr pointer can be directly assigned by weak_ptr or shared_ptr type pointer.
swap(x) where x represents a weak_ptr type pointer of the same type, and this function can interchange 2 The contents of weak_ptr pointers of the same type.
reset() Set the current weak_ptr pointer to a null pointer.
use_count() Look at the number of shared_ptr pointers that point to the same pointer as the current weak_ptr pointer.
expired() Determine whether the current weak_ptr pointer has expired (the pointer is empty, or the heap memory pointed to has been released)
lock() If the current weak_ptr has expired, this function will return an empty shared_ptr pointer; otherwise, this function will return a shared_ptr pointer that points to the same point as the current weak_ptr. .

Again, the weak_ptr template class does not overload the * and -> operators, so the weak_ptr type pointer can only access a certain shared_ptr
The heap memory space pointed to by the pointer cannot be modified.

#include <iostream>
#include <memory>
using namespace std;
int main()
{<!-- -->
    std::shared_ptr<int> sp1(new int(10));
    std::shared_ptr<int> sp2(sp1);
    std::weak_ptr<int> wp(sp2);
    //Output the number of shared_ptr type pointers pointing to the same point as wp
    cout << wp.use_count() << endl;
    //Release sp2
    sp2.reset();
    cout << wp.use_count() << endl;
    //With the help of the lock() function, return a shared_ptr type pointer pointing to the same point as wp, and obtain its stored data
    cout << *(wp.lock()) << endl;
    return 0;
}

1.3 unique_ptr

unique_ptr is an exclusively owned smart pointer that provides strict ownership. It replaces auto_ptr in C++98.

The unique_ptr object wraps a raw pointer and is responsible for its lifetime. When the object is destroyed, the associated raw pointer is deleted in its destructor.

unique_ptr has -> and * operator overloads so it can be used like a normal pointer.

#include <iostream>
#include <memory>
using namespace std;

struct Task {<!-- -->
    int mId;
    Task(int id) :mId(id) {<!-- -->
        cout << "Task::Constructor" << endl;
    }
    ~Task() {<!-- -->
        cout << "Task::Destructor" << endl;
    }
};

int main()
{<!-- -->
    //Create a unique_ptr instance from a raw pointer
    //shared_ptr<Task> taskPtr(new Task(23));
    //int id = taskPtr->mId;

    //Call its lock function to obtain the shared_ptr example and then access the original object.
    //weak_ptr<Task> weak1 = taskPtr;
    //int id = weak1.lock()->mId;

    unique_ptr<Task> taskPtr(new Task(23));
    int id = taskPtr->mId;

    cout << id << endl;

    return 0;
}

Regardless of whether the function exits normally or exits abnormally (due to some exception), the destructor of taskPtr will always be called. Therefore, the raw pointer will always be deleted and prevent memory leaks.

2. Usage steps

1.shared_ptr single use case

Example of resource release failure caused by two shared_ptr pointers referencing each other:

The code is as follows (example):

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class B;
class A
{<!-- -->
public:
    shared_ptr<B> pb_;
    ~A()
    {<!-- -->
        cout << "A delete\
";
    }
};
class B
{<!-- -->
public:
    shared_ptr<A> pa_;
    ~B()
    {<!-- -->
        cout << "B delete\
";
    }
};

void fun() {<!-- -->
    shared_ptr<B> pb(new B());
    cout << "pb.use_count " << pb.use_count() << endl;//1
    shared_ptr<A> pa(new A());
    cout << "pa.use_count " << pa.use_count() << endl;//1

    pb->pa_ = pa;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2
    pa->pb_ = pb;
    //Since share_ptr is a shared resource, the reference count of the resource pointed to by pb will also be increased by 1.
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2
}//When the program ends, the destructors of A and B are not called.

int main()
{<!-- -->
    fun();
    system("pause");
    return 0;
}

Note that you cannot use the following method to make two shared_ptr objects host the same pointer:

A* p = new A(10);

shared_ptr <A> sp1(p), sp2(p);

sp1 and sp2 do not share the same custody count for p, but each has a custody count of p as 1 (sp2 has no way of knowing that p has been hosted by sp1). In this way, p will be destructed when sp1 dies, and p will be destructed again when sp2 dies, which will cause the program to crash.

2.Usage cases of weak_ptr and shared_ptr

The code is as follows (example):

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class B;
class A
{<!-- -->
public:
    weak_ptr<B> pb_weak;
    ~A()
    {<!-- -->
        cout << "A delete\
";
    }
};
class B
{<!-- -->
public:
    shared_ptr<A> pa_;
    ~B()
    {<!-- -->
        cout << "B delete\
";
    }
    void print() {<!-- -->
        cout << "This is B" << endl;
    }
};

void fun() {<!-- -->
    shared_ptr<B> pb(new B());
    cout << "pb.use_count " << pb.use_count() << endl;//1
    shared_ptr<A> pa(new A());
    cout << "pa.use_count " << pa.use_count() << endl;//1

    pb->pa_ = pa;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2

    pa->pb_weak = pb;
    cout << "pb.use_count " << pb.use_count() << endl;//1: Weak references will not increase the reference count of the resource referred to by use_count().
    cout << "pa.use_count " << pa.use_count() << endl;//2

    shared_ptr<B> p = pa->pb_weak.lock();
    p->print();//Methods that cannot directly access objects through weak_ptr must be converted to shared_ptr first
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2
}//When the function ends, call the destructors of A and B

//The reference count of resource B has always been only 1. When pb is destructed, the count of B is reduced by one and becomes 0, and B is released.
//When B is released, A's count will also be reduced by one. At the same time, when pa itself is destructed, resource A's count will also be reduced by one. Then A's count is 0 and A is released.


int main()
{<!-- -->
    fun();
    system("pause");
    return 0;
}

Summary

  1. weak_ptr is a smart pointer used to solve the deadlock problem when shared_ptr refers to each other. If two shared_ptr refer to each other, then the reference count of the two shared_ptr pointers will never drop to 0 and the resource will never be released. weak_ptr is a weak reference to an object. It does not increase the object’s use_count. weak_ptr and shared_ptr can be converted to each other. shared_ptr can be directly assigned to weak_ptr, and weak_ptr can also obtain shared_ptr by calling the lock function.
  2. Weak_ptr pointers are usually not used alone and can only be used with shared_ptr type pointers. Binding a weak_ptr to a shared_ptr does not change the shared_ptr’s reference count. Once the last shared_ptr pointing to the object is destroyed, the object is released. Even if there is a weak_ptr pointing to the object, the object will still be released.
  3. weak_ptr does not overload operator-> and operator
    * operator, so the object cannot be used directly through weak_ptr. The typical usage is to call its lock function to obtain the shared_ptr example and then access the original object.