What is a smart pointer? Why use smart Pointers? How to break the problem of circular references? What is the impact on resource management?

Seeing these problems, I feel uneasy. What is a smart pointer? Why use a smart pointer? What the hell is circular reference? To achieve? I ❌…

Let’s start with the first question, what is a smart pointer?

There are several common intelligent Pointers, one is a shared pointer shared_Ptr, one is an exclusive pointer unique_PTR, one is a weak pointer Weak_PTR, one is a long time unused auto_PTR (replaced by unique_PTR). Smart pointer is also a pointer, it also belongs to the category of Pointers, but it is smarter than the general pointer, indicating that is smart pointer, must be a little smarter. The distinction that resembles present intelligence lives in with common household. So just how smart is it? And we know that dynamic memory, which is when we normally apply memory in the heap, if we use Pointers, looks like this

T *p = new T();
delete p;
Copy the code

For example, delete and new are a pair of operations, delete[] and new[] are both a pair of CP appear, to abuse programmers this group of single wang ðŸķ. Operations such as new allocate space for an object and return a pointer to that object. So a pointer is returned, so we need to manage the pointer. Delete takes a pointer to a dynamic object, destroys the object (calling the destructor, which does nothing except for the built-in type), and frees the memory associated with it. You let a single ðŸķ to manage? Isn’t that funny? < _ < – -! In the use of dynamic memory, problems tend to occur. Once you forget delete or do the wrong thing at the wrong time, or do the right thing at the wrong time, that’s also not correct, or do the wrong thing at the right time, memory leaks tend to occur. What can we do? So the library, defined in the header

, provides smart Pointers, which contribute a lot to building a harmonious society. Smart Pointers behave like regular Pointers except that they are responsible for releasing the object they point to. And the pointer doesn’t tell you who owns the object. You don’t know who owns the object, but the smart pointer does.

One’s own, feel the whole world (unique_ptr exclusive ownership)

    unique_ptr<string> p1(new string("hi,world")); // The initialization must take the form of direct initialization
    unique_ptr<string> p2(p1); // ❌ does not support copy
    unique_ptr<string> p3;
    p3 = p2; // ❌ does not support assignment
Copy the code

The code above makes unique_ptr feel comfortable. What is mine is mine. You can’t keep a copy of it. He’s talking about exclusive ownership. Pointers normally support copy-assignment, but here it deletes both the copy-assignment constructor and the copy-assignment operator and doesn’t let you copy at all.

  • A unique_ptr “owns” an object (to which it points), and only one unique_ptr can point to a given object at any one time. When unique_ptr is destroyed, the object it points to is also destroyed.
  • Unique_ptr cannot be copied, cannot be assigned, can be moved (p.ralase ())
unique_ptr<string> p1(new string("hi"));
unique_ptr<string> p2(p1.release()); // set p1 to null and return pointer
cout << *p2 << endl;
unique_ptr<string> p3(new string("hello,world"));
p2.reset(p3.release()); // reset sets p2 to point to the object pointed to by p3, and release() sets p3 to empty
cout << *p3 << endl; // All output is hi
cout << *p1 << endl; // P1 has been released, no more
Copy the code

What about the implementation of the standard library with all its limitations? Take a look at the following paragraph

Template <typename T,typename D = default_delete<T> > // Default_delete is a stateless class. using pointer = ptr; using element_type = T; using deleter_type = D; constexpr unique_ptr() noexcept; Unique_ptr (nullptr_t) noexcept:unique_ptr(){} // Explicit unique_ptr(pointer p) noexcept; constEXPr explicit unique_ptr(nullptr_t) noexcept: explicit unique_ptr() noexcept; // from pointer unique_ptr(pointer p,typename conditional<is_reference<D>::value,D,const D&> del) noexcept; // lvalue unique_ptr(pointer p,typename remove_reference<D>::type&& del) noexcept; // rvalue unique_ptr(unique_ptr&& x) noexcept; // Rvalue move constructor template<class U,class E> unique_ptr(unique_ptr<U,E> &&x)noexcept; Template <class U> unique_ptr(auto_ptr<U> &&x)noexcept; Unique_ptr(const Unique_ptr &) = delete; Unique_ptr & operator=(unique_ptr&& x) noexcept; Unique_ptr & operator=(nullptr_t) noexcept; // The null pointer type template<class U,class E> unique_ptr& operator=(unique_ptr<U,E>&& x)noexcept; // Cast unique_ptr& operator=(const unique_ptr&) = delete; // Assignment is not allowed;Copy the code

Noexpect is set up so as not to throw exceptions, including rvalue reference operations, lvalue reference operations, and a constructor exception. It basically covers and also explains why there is no copy and no assignment. Both release() and reset() transfer ownership of Pointers from one (nonconst)unique_ptr to another. Reset () is better and can free memory, but release() is not. Release () must have an adapter, which can be automatically freed or manually freed. Now let’s look at how these two are implemented

void reset(pointer p = pointer()) noexcept; // There is a default value pointer Release () noexcept; // This returns a valueCopy the code

Release () returns a pointer, so it needs a pointer.

  • The unique_ptr holds a pointer. When the pointer itself is destroyed (such as thread control flow out of the scope of the unique_ptr), what is the associated deleter used to release the object to which it points? When a unique_PTR is destroyed, its own release is called to destroy the owned object.
deleter_type& get_deleter(a) noexcept;
const deleter_type& get_deleter(a) const noexcept;
Copy the code
  1. The local variable release should do nothing
  2. The memory pool should return the object to the memory pool, and whether it is destroyed depends on how the memory pool is defined.
  3. The default call to delete releases the object management release it points to, which is divided into run-time binding and compile-time binding. These two differences apply to shared_ptr and unique_ptr. We’ll talk about shared_ptr as a whole. Unique_ptr manages releaser-time bindings at compile time. So how do we deliver the release? Let’s take a look at 🌰
#include <memory>
#include <iostream>
#include <string>
using namespace std;

class Role{
    public:
        Role(const string &crole):role(crole){
            cout << role << endl;
        }
        ~Role(){
            cout << "delete" << endl;
        }
        void delRole(a){
            cout << "delete Role outside" << endl;
        }
    private:
        string role;
};

void outdelRole(Role *r){
    r->delRole();
}

int main(a){
    unique_ptr<Role,decltype(outdelRole)*> p1(new Role("trans"),outdelRole);
    return 0;
}
Copy the code

Trans delete Role outside 🌰, we can overload the release, if it is a function release, then its parameter type must be an objT type pointer, in order to delete meaning. Decltype is commonly used to specify the type

unique_ptr<objT,delT>p(new objT,fcn); // FCN is a delT object
Copy the code

It’s up to you how and what you want to delete. You can also do that

#include <iostream>
#include <memory>
using namespace std;
class state_deleter {  // a deleter class with state
  int count_;
public:
  state_deleter() : count_(0) {}
  template <class T>
  void operator() (T* p) {
    cout << "[deleted #" << ++count_ << "]\n";
    deletep; }}; state_deleter del;unique_ptr<int,state_deleter> alpha (new int);
unique_ptr<int,state_deleter> beta (new int,alpha.get_deleter());

// gamma and delta share the deleter "del" (deleter type is a reference!) :
unique_ptr<int,state_deleter&> gamma (new int,del);
unique_ptr<int,state_deleter&> delta (new int,gamma.get_deleter());
Copy the code

Let’s look at some more code for comparing traps

unique_ptr<string> p1;
cout << *p1 << endl;
Copy the code

So this code says p1 is a null pointer, so this null pointer, it doesn’t point to an object, what about this next one?

unique_ptr<string> p1();
cout << *p1 << endl;
Copy the code

The output is 1. Why is that? Unique_ptr

p1() declares a function p1 with no arguments and returns a pointer of type unique_ptr, so if *p1 is a function body, it must be 1

use

  • Providing exception safety for dynamically allocated memory Unique_ptr can be understood as a simple pointer (pointing to an object) or a pair of Pointers (including the release deleter case)
  • Pass ownership of dynamically allocated memory to the function
  • Returns dynamically allocated memory from a function
  • Save the pointer ⚠ïļ from the container there is a use for get()
pointer get(a) const noexcept;
Copy the code

Get () is a pointer or null pointer to an object

unique_ptr<string> p1(new string("hello world"));
string *pstr = p1.get();
cout << *pstr << endl;
Copy the code

Unlike release(), which merely hosts, get does not point to the object that P1 points to, but does not free P1’s memory. PSTR does not acquire ownership of the smart pointer, but only its object. P1 still needs to remove the managed data PSTR at some point. Look again at the dereference operator

typename add_lvalue_reference<element_type>::type operator* ()const;
Copy the code

Function supports pointer operation

unique_ptr<string> p1(new string("hello world"));
cout << *p1 << endl;
Copy the code

Look again at the -> operator

pointer operator- > ()const noexcept;
Copy the code

Operations that support pointer behavior

  unique_ptr<C> foo (new C);
  unique_ptr<C> bar;

  foo->a = 10;
  foo->b = 20;

  bar = std::move(foo); // Support the rvalue move operation foo is freed
Copy the code

Well, we know that the whole unique_ptr supports pointer behavior, so let’s look at a special version of it. What is specialisation? That is, special treatment for special 🌰. Different versions

template<class T.class D> class unique_ptr<T[],D>;
Copy the code
// For built-in functions
unique_ptr<int[]> make_sequence(int n){
    unique_ptr<int[]> p{new int[n]};
    for(int i = 0; i<n; ++i) p[i] = i;return p; // Returns a local object
}
Copy the code

Here, of course, we add the unique [] operator, which overloads the [] operator.

element_type& operator[] (size_t i)const;
Copy the code

Instead of worrying about matching, we provide a special version that just helps the compiler do the matching. What about swapping Pointers? Switching is also a mobile operation.

template <class T.class D>
void(unique_ptr<T,D>& x.unique_ptr<T,D>& y)noexpect;
Copy the code

You take mine, I’ll take yours. Of course, this is a non-member function, and there are ways to write member functions

void swap(unique_ptr& x) noexcept;
Copy the code

That’s a.swoop (B).

Shared object 😁, I share your object âœĻoâœĻ(shared_ptr shared ownership)

Now that we’re done with unique_ptr, let’s talk about sharing Pointers, which makes society better. Let’s see how it works first. Okay

shared_ptr<string> p1; 
shared_ptr<list<int> > p2;
Copy the code

By default, p1 and p2 are null Pointers. Of course, neither operation allocates or uses dynamic memory. How do you do that? Let’s try this.

shared_ptr<string> p1(new string("hehehe"));
cout << *p1 << endl;
Copy the code

You can also try this

shared_ptr<int> clone(int p){
    return shared_ptr<int> (new int(p));
}
Copy the code

You can also manage the built-in pointer inum

int *inum = new int(42);
shared_ptr<int> p2(inum);
Copy the code

Stop ðŸĪš stop ðŸĪš stop 🛑, first of all, what is shared_ptr shared pointer? Shared_ptr represents shared ownership, and unlike unique_ptr, shared_ptr can share an object. Shared_ptr can be used when two pieces of code need to access the same data, but neither has exclusive ownership (responsible for destroying the object). Shared_ptr is a count pointer that releases the object it points to when the count (use_count) turns to 0. Can be interpreted as a structure containing two Pointers, one to an object and the other to a counter (use_count). Unlike unique_ptr, its deleter is a nonmember function only because it destroys the object it points to when the count becomes zero. But it’s a callable object, and I’ll talk about callable objects later, but it’s important to understand that shared_ptr’s releaser is bound at run time, not at compile time. The unique_ptr is the release of the compile-time binding. The default release is DELETE, but this hasn’t changed. (Calls the destructor of an object and frees up free storage) The point is to use counts. How is this count defined? Let’s look at a piece of code.

shared_ptr<int> p3 = make_shared<int> (42);
cout << p3.use_count() << endl;
Copy the code

See, use_count() here is used to count, now 1, and this is the object referenced once.

    shared_ptr<int> p3 = make_shared<int> (42);
    auto r = p3;
    cout << p3.use_count() << endl;
Copy the code

So this is 2, what happens here, increment the reference count for p3, what about r? What is the count of r? R is 2, so this r also refers to p3, so this counter must be the same. What if R had something to point to? The counter of the object to which R refers is also decremented, without affecting other Pointers. So the difference is that none of these shared ownership Pointers have the right to kill objects, he outsources killing objects. I can’t bear it! 😖). So, in that sense, since there is a counter, we can say that shared_ptr automatically destroys managed objects. In other words, shared_ptr automatically frees the associated memory. Take a look at this code to see how dynamic memory is used

#include <iostream>
#include <memory>
#include <string>
#include <initializer_list>
#include <vector>

using namespace std;

class StrBlob{
    public:
        typedef vector<string>::size_type size_type;
        StrBlob():data(make_shared<vector<string> >()){}
        StrBlob(initializer_list<string> il):data(make_shared<vector<string> >(il)){} // Initialize the vector with the argument list
        size_type size(a) const { returndata->size(); }bool empty(a) const { returndata->empty(); }void push_back(const string &t){returndata->push_back(t); }void pop_back(a);
        string &front(a);
        string &back(a);
    private:
        shared_ptr<vector<string> > data; // Share the same data?
        void check(size_type i,const string &msg) const;
};
Copy the code

When we copy, assign, or destroy a StrBlob object, the shared_ptr data member will be copied, assigned, and destroyed. So every time it’s a safe operation, automatic release. It’s safe because of the counter. So it’s not complicated, but hopefully we can use shared_Ptr to manage dynamic memory resources. And I’m going to focus on RAII in a second. Ok, so having looked at resource management in dynamic memory, what do we know about dynamic memory? It’s the cp pair, new and delete. Shared_ptr and new can also be used together.

shared_ptr<double> p1; // shared_ptr can point to a double
shared_ptr<int> p2(new int(42)); // p2 refers to a direct initializer of int 42
Copy the code

Let’s look at constructors

template<typename U> class shared_ptr{ public: using element_type = U; constexpr shared_ptr() noexcept; Constexpr shared_ptr(nullptr_t):shared_ptr(){} template <class U> explicit shared_ptr(U* p); Template <class U,class D> shared_ptr(U* p,D del); Template <class D> shared_ptr(nullptr_t p,D del); Template <class U,class D, class Alloc> shared_ptr(U* p,D del,Alloc Alloc); / / distribution? template <class D,class Alloc> shared_ptr(nullptr_t p,D del,Alloc alloc); shared_ptr(const shared_ptr& x) noexcept; template<class U> shared_ptr(const shared_ptr<U>& x)noexcept; template<class U> explicit shared_ptr(const weak_ptr<U>& x); shared_ptr(shared_ptr&& x)(shared_ptr<U>&& x)noexcept; // Rvalue move template <class U> shared_ptr(auto_ptr<U> &&x); template <class U,class D> shared_ptr(unique_ptr<U,D>&& x); Template <class U> shared_ptr(const shared_ptr<U>& x,element_type* p)noexcept; };Copy the code

Among constructors, smart pointer constructors that take pointer arguments are explicit, which is the explicit construction rather than the implicit conversion.

shared_ptr<int> clone(int p){
    return shared_ptr<int> (new int(p));
}
Copy the code

In Primer, it is recommended not to mix normal and smart Pointers. What counts as a mix? Let’s take a look at the 🌰 it gives.

void process(shared_ptr<int> ptr){
    / / use the PTR
}// The PTR goes out of scope and is destroyed
Copy the code

In this 🌰, the PTR is value passing. As we all know, value passing increases the cost of copying, construction, etc., so the PTR count is at least 2, which is fair enough, and does not go to 0 when the process ends. So the local variable PTR is destroyed, and the memory PTR points to is not freed. (So using a reference reduces the increase in the reference count)

void process(shared_ptr<int>& ptr){
    cout << ptr.use_count() << endl;
    cout << *ptr << endl;
}
Copy the code

When we use value passing, the reference count is at least 2, but with reference passing, the reference count is not incremented

    shared_ptr<int> p3 = make_shared<int> (42);
    cout << p3.use_count() << endl;
    // auto r = p3;
    // cout << r.use_count() << endl;
    process(p3);
    cout << p3.use_count() << endl;
Copy the code

With reference counting, the output is consistent. 🌰 can only be used for reference and value passing. It seems that it has nothing to do with mixing normal Pointers and smart Pointers.

    int *x(new int(9));
    process(shared_ptr<int>(x));
    int j = *x;
    cout << j << endl;
Copy the code

For 🌰 above we used value passing. Well. What does 🌰 mean? Shared_ptr

(x) shared_ptr

(x

shared_ptr<int> ptr = shared_ptr<int> (new int(10));
Copy the code

Got it.

shared_ptr<T> p(q);
Copy the code

Q is the built-in pointer, and P manages the object to which this built-in pointer points. Q must point to memory allocated by new and be converted to T*. So as the above example shows, when the two are used together, the temporary shared_ptr is destroyed and the memory that it points to is freed. So x is probably still pointing to that memory, but x has inadvertently become a null dangling pointer. In fact, when we say that a shared_ptr is bound to an ordinary pointer, we are handing memory management responsibility to the unnamed shared_ptr. Therefore, we cannot or should not use built-in Pointers to access memory pointed to by shared_ptr. Primer also advises against using GET to initialize another smart pointer or assign a value to a smart pointer. The get() function, also described briefly, returns a built-in pointer to an object managed by a smart pointer. It is designed to pass a built-in pointer to code that cannot use a smart pointer if it needs to. What do you mean? It’s just a managed pointer. So let’s look at this code

shared_ptr<int> p(new int(42));
int *q = p.get();
{
    // Two independent shared_ptr points to the same memory
    shared_ptr<int>(q);
    // Release when out of scope
}
int foo = *q; // Undefined at the end
Copy the code

So this explains that you can’t initialize another smart pointer with a get(). After all, get() is managed. It gives you what you already have. Of course, shared_ptr can also use the reset operation

    string *inum = new string("hhh");
    shared_ptr<string> p5 = make_shared<string> ("hi");
    p5.reset(inum);
Copy the code

But it can only be used for built-in pointer passing. Can also pass release to shared_ptr p5.reset(inum, D); So why doesn’t shared_ptr have a release member? I don’t have ownership. With all this talk, make_shared has been kind of ignored.

template <class T.class.Args>
    shared_ptr<T> make_shared(Args&&... args);
Copy the code

This is its source code, his purpose is to make shared_ptr, return type shared_ptr

object, the object has and stores a pointer to it (reference number 1). Let’s see how it works

auto baz =make_shared<pair<int.int> > (30.40); . baz->first .. << baz->secondCopy the code

Ok, so when we initialize with shared_ptr, it’s best and safest to use this library function, and when we use new, we definitely need to convert to give ownership, but make_shared does the allocation, security, and gives you the returned object of type shared_ptr, Just let your pointer point. Recommended use oh! Shared_ptr is a pointer that has a release, a counter, a copy that adds a reference, an assignment that adds a reference, and a decremented reference count. Let’s do another case

struct Node{
    shared_ptr<Node> pPre;
    shared_ptr<Node> pNext;
    int val;
};
void func(a){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout <<p2.use_count() << endl;
}
Copy the code

We see that p1 is 2, p2 is 2, they copy and reference each other! In order to free P2, p1 has to be freed first, and in order to free P1, P2 has to be freed, which is a circular reference. Finally, P1 and P2 can never be freed. That can how to do, the above introduction actually does not have a kind of ability to solve, do not panic, do not busy, quietly in both sides. Look down quietly.

Weak_ptr let quietly continue quietly the go or let you go

So this is a ring up here, so how do we break this ring to free up memory? Use weak_ptr. Weak_ptr, an intelligent pointer that does not control the lifetime of the object it points to, points to objects managed by a shared_Ptr. It seems that this is also the fun of shared ownership, unlike unique_ptr, where one person is alone. What do you mean, out of control? Weak_ptr does not affect shared_ptr reference count. Once shared_ptr is destroyed, the object is also destroyed, even if weak_ptr still points to the object. So, it’s time to let you go. So it’s also called weak shared ownership. Reference only, not count, but there is no, to check expired() arises. Let’s take a look at its construction and use

template <class T> class weak_ptr{
    public:
    constexpr weak_ptr(a) noexcept;
    weak_ptr(const weak_ptr& x) noexcept;
    template <class U> weak_ptr(const weak_ptr<U>& x) noexcept;
    template <class U> weak_ptr(const shared_ptr<U>& x) noexcept;
}
Copy the code

So it can be seen from the constructor that weak_ptr can be constructed by itself and can also point to share_ptr, and it is only a reference.

shared_ptr<int> sp(new int(42));
weak_ptr<int> wp(sp);
cout << wp.use_count << endl;
Copy the code

The use_count?

long int use_count(a) const noexcept;
Copy the code

See? It does not change the reference count. Const so what is expired? It simply checks to see if use_count() is zero and returns false if it is zero and true otherwise.

bool expired(a) const noexcept;
Copy the code

This is used to check whether the object to which the pointer points has been destroyed. Therefore, the object may not exist, so we cannot use weak_ptr to access the object directly, and weak_ptr does not have the process of overloading the access operator *, so we need to call other functions, such as lock

shared_ptr<T> lock() const noexcept;
Copy the code

Lock () checks if the object that Weak_ptr points to exists and returns a shared object, shared_ptr, if so.

#include <iostream>
#include <memory>

int main (a) {
  std: :shared_ptr<int> sp1,sp2;
  std::weak_ptr<int> wp;
                                       // sharing group:
                                       // --------------
  sp1 = std::make_shared<int> (20);    // sp1
  wp = sp1;                            // sp1, wp

  sp2 = wp.lock();                     // sp1, wp, sp2
  sp1.reset();                         // wp, sp2

  sp1 = wp.lock();                     // sp1, wp, sp2

  std: :cout << "*sp1: " << *sp1 << '\n';
  std: :cout << "*sp2: " << *sp2 << '\n';

  return 0;
}
Copy the code

It’s clear. They both output 20. Similarly, reset can empty a Weak_ptr so why, weak_ptr can break it? Let’s move on to the next piece of code

struct Node{
    weak_ptr<Node> pPre; / / the difference between ⮅ ïļ ⮅ ïļ ⮅ ïļ
    weak_ptr<Node> pNext; / / the difference between ⮅ ïļ ⮅ ïļ ⮅ ïļ
    int val;
    Node(){
        cout << "construct" << endl;
    }
    ~Node(){
        cout << "delete" <<endl; }};void func(a){
    shared_ptr<Node> p1(new Node());
    shared_ptr<Node> p2(new Node());
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
    p1->pNext = p2;
    p2->pPre = p1;
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
}
Copy the code

This breaks the loop of circular references, because each shared_ptr will set the reference count to 1 and increment every time it is used, so if it is not incremented, it will just use the same object as before. Change the structure and you can call the destructor.

Shared_ptr and unique_PTR release device a static display magic

Finished talking about weak_Ptr, suddenly feel, the invention of intelligent pointer is really great! Single ðŸķ Easy mistakes made when confused become no longer easy. So, every time we find that these two Pointers, there’s going to be a release. Unique_ptr version

unique_ptr<T,D> up;
Copy the code

From version

shared_ptr<T> p(q,d);
Copy the code

Case insensitive d is delete, release. Unique_ptr as we introduced earlier, this is a determinate remover whose type is determined at compile time. unique_ptr

template<typename T,typename D = default_delete<T> > Default_delete is a stateless class
class unique_ptr{
    public:
        using pointer = ptr;
        using element_type = T;
        usingdeleter_type = D; .Copy the code

From the wide

template<typename U> class shared_ptr{ public: using element_type = U; constexpr shared_ptr() noexcept; Constexpr shared_ptr(nullptr_t):shared_ptr(){} template <class U> explicit shared_ptr(U* p); // Explicit constructs do not have implicit conversions...Copy the code

As you can see from the template, shared_ptr has never had a fixed type release. Although the default is DELETE, callable objects can also be used

#include <iostream>
#include <memory>

int main (a) {
   auto deleter = [](Node* p){
    cout << "[deleter called]\n"; 
    delete p;
    };
    // shared_ptr<int> foo (new int,deleter);
    // cout << "use_count: " << foo.use_count() << '\n';
    shared_ptr<Node> bar(new Node(),deleter);
  return 0;                        // [deleter called]
}
Copy the code

So releasers, either unique_ptr or shared_ptr, must be saved as a pointer or as a class that encapsulates a pointer. But we can also be sure that shared_ptr does not save the release directly as a member because its type is not known until run time. Since shared_ptr has only one template argument and unique_ptr has two, the way unique_ptr works is that the release type is part of the unique_ptr type. So the release can be stored directly in the unique_ptr object. Both relevers invoke the user-provided releaser or perform delete on their saved Pointers. So, to summarize, unique_ptr avoids the runtime overhead of calling the release indirectly by binding the release at compile time. By binding the release at run time, shared_ptr makes it easier for users to reload the release. So these are all examples of object management of resources, one by one, shareD_ptr, unique_Ptr are in the form of object management of resources, prevent resource leakage, dynamic memory is no longer afraid of leakage. Well, what are callable objects? How do you use it? Why can shared_ptr use callable objects like this? Published this article, I hope the backend of the big cattle casual spray, younger brother will be improved 😊. Another: writing is not easy, reprint please indicate the source

To be continued…