Summary of smart pointers in C++

smart pointer

一、shared_ptr

    shared_ptr uses shared ownership to manage the lifetime of the pointed object. It provides a reference counting mechanism to record how many shared_ptr points to the same dynamic memory space. Only when the last shared_ptr is destroyed will the memory be released automatically.

1. Quickly use shared_ptr

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

class MyClass {
public:
    MyClass() {
        cout << "MyClass constructed!" << endl;
    }
    ~MyClass() {
        cout << "MyClass destoryed!" << endl;
    }
};

int main() {

    shared_ptr<MyClass> sp1(new MyClass());
    shared_ptr<MyClass> sp2(sp1);

    cout << sp1.get() << " :" << sp1.use_count() << endl; // 2
    cout << sp2.get() << " :" << sp2.use_count() << endl; // 2

    return 0;
}

    In the above code, both sp1 and sp2 are shared_ptr, and sp1 is constructed by the address returned by the new operator, which manages the MyClass() object; sp2 manages the memory of the same object with sp1 through the copy constructor. Therefore, they use the same control block, and the value of the reference counter is 2.

2. Why do you often use make_shared to create a shared_ptr instead of directly using the shared_ptr constructor?

    Compared with using the shared_ptr constructor directly, using make_shared has two main advantages: ① high memory allocation efficiency, ② exception safety.

① High memory allocation efficiency

    Using shared_ptr's constructor to create a shared_ptr object requires memory allocation twice:

shared_ptr<MyClass> sp1(new MyClass());

    In the above code, first allocate memory for MyClass, and then allocate memory for the control block (including reference counters, etc.), as shown in the following figure:

    

    With make_shared, memory only needs to be allocated once:

shared_ptr<MyClass> sp1 = make_shared<MyClass>();

    In the above code, memory will be allocated for MyClass and the control block at one time, as shown in the following figure:

②, abnormal security

    In the following case, using make_shared will not cause a memory leak, but using the constructor of shared_ptr will cause a memory leak:

func(shared_ptr<int>(new int(12)), shared_ptr<int>(new int (20)))

    Since C++ does not guarantee the order of parameter evaluation, it may be the following order:

new int(12)
new int(20)
shared_ptr
shared_ptr

    In the above sequence, if an exception occurs in the second step new int(20), the memory allocated by the first step new int(12) will not be managed by shared_ptr, resulting in a memory leak, which can be avoided by using make_shared . Because make_shared allocates memory for the management object and the control block together, once the memory of the management object is allocated, there must be a control block to manage it.

    The use of make_shared is as follows:

func(make_shared<int>(12), make_shared<int>(20))

Two, weak_ptr

1. Use weak_ptr quickly

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

class MyClass {
public:
    MyClass() {
        cout << "MyClass constructed!" << " - - " << this << endl;;
    }
    ~MyClass() {
        cout << "MyClass destoryed!" << endl;
    }
};

int main() {

    shared_ptr<MyClass> sp1 = make_shared<MyClass>();
    weak_ptr<MyClass> wp1(sp1);

    return 0;
}

2. How does weak_ptr help shared_ptr solve circular reference problems?

    In the following code, a shared_ptr circular reference occurs, causing the destructor to fail to be called normally.

#include <iostream>
#include <memory>
using namespace std;
class YourClass;
class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructed!" << endl;
    }
    ~MyClass() {
        std::cout << "MyClass destoryed!" << endl;
    }
    void set_ptr(std::shared_ptr<YourClass>& ptr) {
        m_ptr_your = ptr;
    }
private:
    shared_ptr<YourClass> m_ptr_your;
};

class YourClass {
public:
    YourClass() {
        std::cout << "YourClass constructed!" << endl;
    }
    ~YourClass() {
        std::cout << "YourClass destoryed!" << endl;
    }
    void set_ptr(std::shared_ptr<MyClass>& ptr) {
        m_ptr_my = ptr;
    }
private:
    shared_ptr<MyClass> m_ptr_my;
};

int main()
{
    shared_ptr<MyClass> ptr_my(new MyClass());
    shared_ptr<YourClass> ptr_your(new YourClass());

    ptr_my->set_ptr(ptr_your);
    ptr_your->set_ptr(ptr_my);

    return 0;
}

    The result of running the code is as follows:

MyClass constructed!
YourClass constructed!

    As can be seen from the running results, the code does not call the destructor correctly.

    The circular reference situation is shown in the figure below:

    

    If you change the pointer type of m_ptr_your in MyClass from shared_ptr to weak_ptr, then it will break the problem of circular reference and thus correctly call the destructor. After modifying the code, the running result is as follows:

MyClass constructed!
YourClass constructed!
YourClass destoryed!
MyClass destoryed!

    It can be seen from the running results that since m_ptr_your uses a weak_ptr pointer, YourClass will call the destructor first, and then MyClass will call the destructor.

Three, unique_ptr

    Unique uses exclusive ownership to manage the lifetime of the object it points to. That is to say, at the same time, there can only be one unique_ptr pointer pointing to this object. When this unique_ptr is destroyed, the object it points to will also be destroyed.

1. Quickly use unique_ptr

#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructed!" << endl;
    }
    ~MyClass() {
        std::cout << "MyClass destoryed!" << endl;
    }
};

int main()
{
    unique_ptr<MyClass> up1(new MyClass());
    unique_ptr<MyClass> up2(up1);  // 不允许,unique_ptr禁用了拷贝构造函数
    unique_ptr<MyClass> up3 = up1; // 不允许,unique_ptr禁用了重载赋值运算符

    return 0;
}

Guess you like

Origin blog.csdn.net/hu853712064/article/details/120280039