C++ New Classic | C++ Checking and Filling in Lapse (Smart Pointers)

Table of contents

1. Dynamic allocation

1. Three ways of initialization

2. Release memory

(1) Dangling pointer

 3. Create new projects and observe memory leaks

2. In-depth understanding of new/delete keywords

1.new and delete

2.operator new()和operator delete()

3. Apply for and release an array

3. Smart pointers

 1.shared_ptr

(1) Conventional initialization

(2)make_shared

(3) Reference counting

(3.1) Reference count increased

(3.2) Reference count reduction

(4) Common operations on shared_ptr pointers

(4.1) use_count member function

(4.2) unique member function

(4.3) reset member function

(4.4) get member function

(4.5) swap member function

(5) Custom deleter

(6) Usage suggestions, pitfalls and taboos

(6.0) Prioritize using make_shared to construct smart pointers

(6.1) Use raw pointers with caution

(6.2) Use the pointer returned by get with caution

(6.3) Use enable_shared_from_this to return this

(6.4) Avoid circular references to each other

(7)Move semantics

2.weak_ptr

 (1) Common operations of weak_ptr

(1.1) use_count member function

(1.2) expired member function

(1.3) reset member function

(1.4) lock member function

(2) Size problem

(3) Control block

3.unique_ptr

(1) Conventional initialization

(2)make_unique

(3) Common operations

(3.1) Move semantics

(3.2) release member function

(3.3) reset member function

(3.4) get member function

(3.5) Convert to shared_ptr type

(4) Size problem

(5)Deleter

4. Selection of smart pointers


1. Dynamic allocation

        The method in which the programmer uses new to allocate memory for an object (new returns a pointer to the object) is called dynamic allocation. This method of dynamically allocating memory is equivalent to directly managing memory, specifically for the application of new and delete.

    int *p = new int;   //*p为一个随机值

        This allocated int object actually has no name, or is an unnamed object, but new can return a pointer to the object, and the nameless int object can be operated through this pointer.

1. Three ways of initialization

        For many built-in type (such as int type) objects, if the initial value is not actively provided, the initial value is undefined (uncertain), as shown in the code above.

        There is another way of saying initialization called value initialization . For a self-defined class, when new an object of that class, the so-called value initialization is meaningless. So it makes sense to have a built-in type like int. Take a look at how value initialization is written:

    int *p1 = new int();//值初始化,*p为0

        Of course, you can also initialize an object while new :

    int *p2 = new int(100);//new一个对象的同时进行初始化,*p为100

2. Release memory

        To delete a piece of memory, you can only delete it once, not multiple times. Of course, you can delete a null pointer multiple times, but it has no practical significance.

        It is recommended that after deleting a pointer, the pointer will be set to null (p=nullptr;). Because even if a pointer is deleted, the pointer still stores the address of the dynamic memory it points to. At this time, the pointer is called a dangling pointer (also called a wild pointer. Programmers can no longer operate the memory pointed to by this pointer). Then if you give the pointer a nullptr, it means that the pointer does not point to any memory, which is a good habit.

        Const object values ​​cannot be changed, but can be deleted:

   const int *p = new int(100);  
   delete p;
   p = nullptr;

(1) Dangling pointer

A dangling pointer (also called a wild pointer) is a pointer to a memory block that has been freed or deleted. In C++, this usually happens in the following situations:

1. When an object is deleted, but one or more pointers still refer to it.
2. When the function returns, the address of the local variable is still retained.
3. When dynamically allocated memory has been released, but there are still pointer references.

Using dangling pointers can cause program crashes, data corruption, and unpredictable behavior. To avoid this, you should ensure that all relevant pointers are set to nullptr after deleting the object or freeing the memory, and do not return the address of local variables.

int* ptr = new int(5); // 动态分配内存
delete ptr; // 释放内存
ptr = nullptr; // 将ptr设置为nullptr以避免成为悬空指针

        Note that in C++11 and later, setting a smart pointer to null after delete automatically prevents dangling references.

std::unique_ptr<int> ptr(new int(5)); // 使用智能指针
ptr.reset(); // 释放内存并将ptr设置为空

 3. Create new projects and observe memory leaks

        You can use "MFC Application" to observe memory leaks. The characteristic of "MFC application" is: when the program ends (exits), if the program has a memory leak, "MFC application" can report it.

        The words "Detected memory leaks!" translated into Chinese indicate that a memory leak has occurred. And there are two leaks, one is 8 bytes, the other is 28 bytes, a total of 36 bytes are leaked.

2. In-depth understanding of new/delete keywords

1.new and delete

        New and delete are both keywords (also called operators/operators) and are not functions. malloc and free are used in C language programming, while new and delete are used in C++ programming.

        One of the most obvious differences between new/delete and malloc/free is that when new is used to generate a class object, the system will call the constructor of the class. When delete is used to delete a class object, the system will call the destructor (release function) of the class. . Since there is the ability to call constructors and destructors, this means that new and delete have the ability to initialize (put the initialization code in the constructor of the class) and release (put the release-related code in the memory allocated by the heap) (in the destructor of a class) capabilities, which malloc and free do not have.

2.operator new()和operator delete()

        operator new(…) and operator delete(…) are actually functions. So, what is the relationship between these two functions and the new/delete operator?

(1) The new operator does two things: ① allocate memory; ② call the constructor to initialize the memory. How does the new operator allocate memory? The new operator allocates memory by calling operator new(...). This function can be called directly, but few people do this.

(2) The delete operator also does two things: ① Call the destructor; ② Release the memory. The delete operator releases memory by calling operator delete().

3. Apply for and release an array

        When dynamically allocating memory for an array, you often need to use [], such as new[...], and when releasing an array, you often need to use [], such as delete[...], which means that new[] and delete[ are often used. ] to be used together.

class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    ~My_Class()
    {
        std::cout << "析构函数"<<std::endl;
    } 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete[] myClass;                     //调用两次析构函数

    return 0;
}

        Why does this call cause the class's destructor to be called twice? How does the system know how many array elements (class objects) new has produced when new is executed?

        The approach of C++ is to allocate 4 more bytes when allocating the array space, specifically to save the size of the array. When deleting [], you can take out the number of the array size, and you will know how many times the destructor needs to be called.

       For built-in types such as int type, there is no way to call the destructor of the class when deleting (only when a class object or an array of class objects is deleted), there is no way to call the destructor of the class, so the system does not have much to do when using new. Allocate 4 bytes of memory.

  • If you comment out the destructor of the class, use new[] to allocate memory for the object array, and use a separate delete to release the memory, there will be no exception.
class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    //~My_Class()
    //{
    //    std::cout << "析构函数"<<std::endl;
    //} 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete myClass;                       //没问题

    return 0;
}
  • If a class writes its own destructor, uses new[] to allocate memory for the object array, and uses a separate delete to release the memory, an exception will be reported. 
class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    ~My_Class()
    {
        std::cout << "析构函数"<<std::endl;
    } 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete myClass;                       //会出异常

    return 0;
}

        The reason for the exception is that the line of code "delete myClass;" does two things:

(1) Call the destructor of the class once. When new is used, two objects are created, the constructor is called twice, and the destructor is called once when released. Although it is not fatal, there are also sequelae (such as if memory is allocated in the constructor of the class, Expecting to release memory in the destructor, if the destructor is executed less than once, it will directly lead to a memory leak).

(2) Call "operator delete(myClass);" to release memory. The exception reported by the system is actually caused by the call to execute this line of code. It is because of the problem of allocating 4 more bytes that the released memory space is disordered. 

         Therefore, if an object uses new[] to allocate memory, but uses delete (instead of delete[]) to release the memory, then the conditions that this object meets are: the object type is a built-in type (such as int type) or Class type without custom destructor.

3. Smart pointers

        Directly new an object returns a pointer to an object. Many people call the pointer of this object a bare pointer. The so-called naked refers to the pointer that is directly new without any packaging. Obviously, this kind of pointer is powerful and flexible to use. At the same time, developers must be responsible for maintaining it throughout the entire process. It is easy to make mistakes if you are not careful. Once used incorrectly, the problems may be huge.

        There are 4 types of smart pointers in the C++ standard library, namely std::auto_ptr, std::unique_ptr, std::shared_ptr, and std::weak_ptr. Each of them has applicable occasions, and they exist to help programmers manage the life cycle of dynamically allocated objects (new objects). Since smart pointers can manage the life cycle of objects, they can effectively prevent memory leaks.

Currently std::auto_ptr has been completely replaced by std::unique_ptr, so do not use std::auto_ptr anymore. The C++11 standard also deprecates the use of std::auto_ptr.

There are some limitations (defects) in the use of auto_ptr. For example, auto_ptr cannot be saved in a container, and auto_ptr cannot be returned from a function.

  • shared_ptr is the concept of shared pointer. Multiple pointers point to the same object. When the last pointer is destroyed, the object will be released.
  • The smart pointer weak_ptr is used to assist the work of shared_ptr.
  • unique_ptr is an exclusive pointer concept. Only one pointer can point to the object at the same time. Of course, the ownership of the object can be transferred.

 1.shared_ptr

        The shared_ptr pointer uses shared ownership to manage the lifetime of the object pointed to. Therefore, the object can not only be owned by a specific shared_ptr, but can be owned by multiple shared_ptr. Multiple shared_ptr pointers cooperate with each other to ensure that the pointed object is released when it is no longer needed.

        When deciding whether to use this kind of smart pointer, you must first think about a question: whether the object pointed to needs to be shared. If it needs to be shared (similar to multiple pointers needing to point to the same memory), then use shared_ptr. If you only need If it is exclusive (only one pointer points to this memory), it is recommended to use a unique_ptr smart pointer, because although shared_ptr does not have much additional overhead, there will still be some additional overhead for sharing.

        The working mechanism of shared_ptr is to use reference counting. Each shared_ptr points to the same object (memory), so obviously, only when the last shared_ptr pointer pointing to the object no longer points to the object, the shared_ptr will be destructed. Object.

(1) Conventional initialization

    std::shared_ptr<std::string> p1;   //默认初始化,智能指针里面保存的是一个空指针nullptr(可以指向类型为string的对象)。

    std::shared_ptr<std::string> p2 = new std::string("abc"); //错误,智能指针是被explicit修饰的,不支持隐式转换(带等号一般为隐式转换)

    std::string *str = new std::string("abc");
    std::shared_ptr<std::string> p3 (str);     //虽然裸指针可以初始化智能指针,但是不推荐智能指针和裸指针穿插使用
 
    std::shared_ptr<std::string> p4(new std::string("abc"));  //正确

(2)make_shared

        The make_shared function is a function template in the standard library and is considered to be the safest and more efficient function template for allocating and using shared_ptr smart pointers. It is able to allocate and initialize an object in dynamic memory (heap) and then return a shared_ptr pointing to this object.

       Note: Use the make_shared method to generate a shared_ptr object, then there is no way to customize the deleter.

std::shared_ptr<int> p = std::make_shared<int>(10);

(3) Reference counting

(3.1) Reference count increased

        Each shared_ptr is associated with a reference count. When the following situations occur, the reference count of all shared_ptr pointing to this object will be increased by 1.

  • Initializing the p2 smart pointer with the p1 smart pointer will cause all shared_ptr reference counts pointing to the object (memory) to increase by 1.
    std::shared_ptr<A> p2 = std::make_shared<A>(10, 10);
    std::shared_ptr<A> p3(p2);                 //也可写成这样:auto p3(p2);
    std::cout << p3.use_count() << std::endl;  //2
  • Pass the smart pointer to the function as an actual parameter (the reference count of this pointer will be restored after the function is executed
    ). But if you pass a reference as a formal parameter, the reference count will not be incremented.
void Test(std::shared_ptr<int> num)
{
    std::cout << num.use_count() << std::endl;       //3
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10); //1
    auto p1(p);//2
    Test(p1);  //3
    std::cout << p1.use_count() << std::endl;        //2
}
void Test(std::shared_ptr<int>& num)
{
    std::cout << num.use_count() << std::endl;       //2
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10);//1
    auto p1(p);//2
    Test(p1);
}
  • As the return value of a function and received by a variable.
std::shared_ptr<int> Test(std::shared_ptr<int>& num) //注意是引用
{
    std::cout << num.use_count() << std::endl;       //2
    return num;
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10);
    auto p1(p);
    auto p2 = Test(p1);
    std::cout << p1.use_count() << std::endl;        //3
}
(3.2) Reference count reduction
  • Assign a new value to shared_ptr and let the shared_ptr point to a new object.
  • The local shared_ptr leaves its scope.
  • When a shared_ptr reference count becomes 0, it automatically releases the object it manages.

(4) Common operations on shared_ptr pointers

(4.1) use_count member function

        This member function is used to return how many smart pointers point to an object. This member function is mainly used for debugging purposes and may not be efficient.

(4.2) unique member function

        Whether the smart pointer exclusively points to an object, that is, if there is only one smart pointer pointing to an object, unique returns true, otherwise it returns false

(4.3) reset member function
  • When reset takes no parameters. If pi is the only pointer pointing to the object, release the object pointed to by pi and make pi empty. If pi is not the only pointer pointing to the object, the object pointed to by pi will not be released, but the reference count pointing to the object will be decremented by 1, and pi will be made empty.
  • When reset takes a parameter (usually a pointer from new). If pi is the only pointer pointing to the object, release the object pointed to by pi and let pi point to new memory. If pi is not the only pointer to the object, the object pointed to by pi will not be released, but the reference count pointing to the object will be decremented by 1, and pi will point to new memory.
  • A null pointer can also be reinitialized by reset.
(4.4) get member function

        p.get(): Returns the pointer saved in smart pointer p. Use with caution. If the smart pointer releases the object it points to, the object pointed to by the returned pointer will become invalid.

        Why do we need such a function? Mainly considering that the parameters of some functions require a built-in pointer (raw pointer), so the built-in pointer needs to be obtained through get and passed to such a function. But be careful not to delete the obtained pointer, otherwise unpredictable consequences will occur.

(4.5) swap member function

        Used to exchange the objects pointed to by two smart pointers. Of course, because it is exchanged, the reference count does not change.

(5) Custom deleter

        A smart pointer can automatically delete the object it points to at a certain time. By default, shared_ptr uses the delete operator as the default way to delete the object it points to.

        Programmers can specify their own deleter, so that when a smart pointer needs to delete an object it points to, it no longer calls the default delete operator to delete the object, but calls the deleter provided by the programmer to delete it. The object it points to. Under normal circumstances, the default deleter works very well, but there are some situations where you need to specify your own deleter because the default deleter cannot handle it - when you use shared_ptr to manage dynamic arrays, you need to specify your own deleter. The default The deleter does not support array objects.

        In fact, when defining an array, add "[]" between the angle brackets "<>", and the memory can be released normally even if you don't write your own deleter.

std::shared_ptr<int[]> p4(new int[10]{0});

         The method of specifying a deleter in shared_ptr is relatively simple. Generally, you only need to add the specific deleter function name in the parameter (note that the deleter is a function with a single parameter). The deleter can also be a lambda expression.

void My_Deleter(int* a)
{
    delete a;
}
void main()
{
    ///自定义删除器   lambda表达式形式
    std::shared_ptr<int> p4(new int(10), [](int *p) {
        delete p;
        });

    ///自定义删除器  函数指针形式
    std::shared_ptr<int> p5(new int(10), My_Deleter);
}

        Another point worth noting: Even if the deleters specified by two shared_ptr are different, as long as they point to the same object, then the two shared_ptr also belong to the same type. The obvious advantage of the same type is that it can be placed in a container whose element type is the object type, making it easier to operate.

    std::vector<std::shared_ptr<int>> vec{ p4,p5 };

(6) Usage suggestions, pitfalls and taboos

(6.0) Prioritize using make_shared to construct smart pointers

        Prioritize using make_shared to construct smart pointers. The compiler will have some special processing for memory allocation, so make_shared will be more efficient, such as eliminating duplicate code, improving security, etc.

    std::shared_ptr<std::string> p1(new std::string("Hello world"));

        The above line of code will allocate memory at least twice. The first time is to allocate memory for the instance of type string (thereby saving the string "Hello world"), and the second time is to allocate memory for the shared_ptr control block in the shared_ptr constructor.

        For make_shared, the compiler will only allocate memory once. This memory allocation is large enough to save both the string ("Hello world") and the control block at the same time:

std::shared_ptr<std::string> p2 = std::make_shared<std::string>("Hello world");
(6.1) Use raw pointers with caution

        If an ordinary raw pointer is tied to a shared_ptr, the memory management responsibility is handed over to the shared_ptr. At this time, the raw pointer (built-in pointer) should no longer be used to access the memory pointed by shared_ptr.

        Although raw pointers can initialize shared_ptr, be careful not to use raw pointers to initialize multiple shared_ptrs. Even if you use a raw pointer, pass the new operator directly instead of passing a raw pointer variable.

(6.2) Use the pointer returned by get with caution

        Never use the pointer obtained by get to initialize another smart pointer or assign a value to another smart pointer.

(6.3) Use enable_shared_from_this to return this

        enable_shared_from_this is a class template, and its type template parameter is the class name of the subclass that inherits it. There is a weak pointer weak_ptr in this class template. This weak pointer can observe this. When the shared_from_this method is called, this method actually calls the lock method of this weak_ptr. The lock method will increase the shared_ptr pointer count by 1 and return the shared_ptr. .

(6.4) Avoid circular references to each other

        Circular references to each other can cause memory leaks.

(7)Move semantics

        In shared_ptr smart pointers, there is also the concept of move semantics. Copying will increment the strong reference count of shared_ptr, while moving will not increment the strong reference count of shared_ptr.

void main()
{
    std::shared_ptr<int> p1 = std::make_shared<int>(10);  
    std::shared_ptr<int> p2 = std::move(p1); 
    std::cout << p1.use_count() << std::endl;    //0
    std::cout << p2.use_count() << std::endl;    //1
}

2.weak_ptr

        The smart pointer weak_ptr is used to assist the work of shared_ptr. Weak is translated into Chinese as "weak". Weak and strong are antonyms. So who does "strong" refer to? It is easy to imagine that the strong pointer is shared_ptr, and the weak pointer is weak_ptr.

        Binding a weak_ptr to a shared_ptr does not change the reference count of the shared_ptr (more precisely, the construction and destruction of a weak_ptr does not increase or decrease the reference count of the pointed object). When shared_ptr needs to release the object pointed to, it is released as usual, regardless of whether there is a weak_ptr pointing to the object. This is the reason why weak is "weak" - it has weak capabilities (weak sharing/weak reference: sharing objects pointed to by other shared_ptr) and cannot control the lifetime of the pointed object.

        Weak reference can be understood as being used to monitor the life cycle of shared_ptr (strong reference). It is an expansion of shared_ptr. It is not an independent smart pointer and cannot be used to operate the resource pointed to, so it looks like a Shared_ptr's helper (spectator) feels like this. So its intelligence is only as smart as being able to monitor whether the object it points to exists. Of course there are some additional uses.

        The references represented by the objects pointed to by shared_ptr all refer to strong references, while the references represented by the objects pointed to by weak_ptr all refer to weak references. Programmers cannot use weak_ptr to directly access objects. They must use a member function called lock. The function of lock is to check whether the object pointed to by weak_ptr still exists. If it exists, lock can return a shared_ptr pointing to the shared object ( Of course, the reference count of the original shared_ptr will be +1). If it does not exist, an empty shared_ptr will be returned.

    std::shared_ptr<int> p6 = std::make_shared<int>(10); // shared_ptr指向的对象代表的引用统统指的都是强引用
    std::weak_ptr<int> w_ptr(p6);                        // weak_ptr所指向的对象代表的引用统统都是弱引用,p6引用计数还是1

    auto temp = w_ptr.lock();                            //如果w_ptr所指的对象存在,并且有temp接收,p6引用计数加1
    if (temp)
    {
        std::cout << p6.use_count() << std::endl;        //2
    }

 (1) Common operations of weak_ptr

(1.1) use_count member function

        Get the number of other shared_ptrs that share the object with this weak pointer, or get the reference count (strong reference count) of the currently observed resource.

(1.2) expired member function

        It means whether it has expired. If the use_cout of the pointer is 0 (indicating that the object pointed to by the weak pointer no longer exists), it returns true, otherwise it returns false. In other words, determine whether the observed object (resource) has been released.

(1.3) reset member function

        Setting the weak reference pointer to null will not affect the number of strong references pointing to the object, but the number of weak references pointing to the object will be reduced by one.

(1.4) lock member function

        Get the monitored shared_ptr.

(2) Size problem

        The size of weak_ptr (that is, size or sizeof) is the same as the size of the shared_ptr object, which is twice the size of the raw pointer. Under the current Visual Studio x86 platform, the sizeof value of a raw pointer is 4 bytes. The sizes of weak_ptr and shared_ptr are both 8 bytes, and these 8 bytes contain two raw pointers:

  • The first raw pointer points to the object pointed to by the smart pointer.
  • The second raw pointer points to a large data structure (control block). This control block contains:
    • The reference count of the pointed object.
    • The weak reference count of the pointed object.
    • Other data, such as the pointer of the custom deleter (if a custom deleter is specified), etc.

        The control block is actually created by shared_ptr. Then, when the shared_ptr object is used to create a weak_ptr object, the weak_ptr object also points to this control block.

(3) Control block

        The control block follows the class, and its size may be more than ten bytes or more. If a deleter is specified, the number of bytes here may be slightly larger. This control block is created from the first shared_ptr pointing to a specified object. Because the weak_ptr object is also created through shared_ptr, the weak_ptr object also uses this control block created by shared_ptr.

        Control the timing of block creation:

  • make_shared: allocates and initializes an object and returns the shared_ptr pointing to this object. So make_shared always creates a control block.
  • When using a raw pointer to create a shared_ptr object. (When I explained the pitfalls of using shared_ptr earlier, I emphasized that you should not initialize multiple shared_ptr with raw pointers, otherwise multiple control blocks, that is, multiple reference counts, will be generated, which will cause the object pointed to to be destructed multiple times, causing complete confusion. Causes the program to run abnormally.)

3.unique_ptr

        When it comes to using smart pointers, generally speaking, the first thing that comes to mind and the first choice to choose is the unique_ptr smart pointer. The unique_ptr smart pointer is an exclusive smart pointer, or it can be understood as the concept of exclusive ownership. That is to say, at the same time, there can only be one unique_ptr pointer pointing to this object (this piece of memory). When this unique_ptr is destroyed, the object it points to will also be destroyed.

(1) Conventional initialization

    std::unique_ptr<A> p = new A(10,10);        //错误,智能指针被explicit修饰,不支持隐式转换(带等号一般为隐式转换)
    std::unique_ptr<A> p(new A(10,10));           //正确

    A* a = new  A(10, 10);                        //不推荐智能指针和裸指针穿插用
    std::unique_ptr<A> p1(a);

(2)make_unique

        There is no make_unique function in C++11, but this function is provided in C++14. Compared with regular initialization, it is also preferable to use the make_unique function, which represents higher performance. Of course, like make_shared, if you want to use a deleter, you cannot use the make_unique function, because make_unique does not support the syntax for specifying a deleter.

    std::unique_ptr<A> pp = std::make_unique<A>(10, 10);  //C++14,推荐

(3) Common operations

(3.1) Move semantics

        Unique_ptr does not allow actions such as copying and assignment. It is a type that can only be moved but not copied. You can use std::move to move a unique_ptr to another unique_ptr.

        unique_ptr smart pointers cannot be copied. But there is an exception. If the unique_ptr is to be destroyed, it can still be copied. The most common is to return a unique_ptr from the function.

    std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");

    std::unique_ptr<std::string> uni_ptr2 = std::move(uni_ptr1);//转移后,uni_ptr1为空
(3.2) release member function

        Give up control of the pointer (severing the connection between the smart pointer and the object it points to), return the pointer (bare pointer), and leave the smart pointer empty. The returned raw pointer can be released by manual delete, or it can be used to initialize another smart pointer, or assign a value to another smart pointer.

    std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
    std::string *str = uni_ptr1.release();
    delete str;
(3.3) reset member function

        When reset takes no parameters, the object pointed to by the smart pointer is released and the smart pointer is made empty. When reset takes parameters, the memory pointed to by the smart pointer is released and the smart pointer points to the new memory.

(3.4) get member function

        Returns the object saved in the smart pointer (raw pointer). Use with caution; if the smart pointer releases the object it points to, the returned object will become invalid.

(3.5) Convert to shared_ptr type

        If unique_ptr is an rvalue, it can be assigned to shared_ptr. The template shared_ptr contains an explicit constructor that can be used to convert the rvalue unique_ptr to a shared_ptr, which will take over the object originally owned by unique_ptr.

        The conversion method can return unique_ptr through the function and use shared_ptr to receive it; it can also use the std::move method.

        When a shared_ptr is created, its internal pointer points to a control block. When converting unique_ptr to shared_ptr, the system will also create a control block for this shared_ptr. Because unique_ptr does not use control blocks, only shared_ptr uses control blocks.

(4) Size problem

        unique_ptr is basically the same as a raw pointer: small enough and fast enough to operate. However, if a deleter is added, the size of unique_ptr may not change, or it may change.

  • If the deleter is an anonymous object such as a lambda expression, the size of unique_ptr will not change.
  • If the deleter is a function, the size of unique_ptr will change.

        Increasing the size of unique_ptr will definitely have a certain impact on efficiency, so you should use it with caution when using a function as a deleter. This is different from shared_ptr, which is twice the size of a raw pointer no matter what deleter is specified.

(5)Deleter

        The deleter of unique_ptr is relatively complicated. You must first pass the deleter type name in the type template parameter, and then give the specific deleter name in the parameter.

        When creating a shared_ptr, shared_ptrs with different deleters but the same pointing type (type of pointed object) can be placed in the same container. But when it comes to unique_ptr, if the deleters are different, it means that the entire unique_ptr type is different. Then, there is no way to put unique_ptr smart pointers of different types into the same container.

void My_Deleter(int* a)
{
    delete a;
    a = nullptr;
}

void main()
{
    ///自定义删除器   lambda表达式形式
    using fun = void(*)(int*);
    std::unique_ptr<int,fun> p4(new int(10), [](int *p) {
        delete p;
        p = nullptr;
        });

    ///自定义删除器  函数指针形式
    typedef void(*fun)(int*);
    std::unique_ptr<int,fun> p5(new int(10), My_Deleter);
}

4. Selection of smart pointers

        If you want to use multiple pointers to the same object in your program, you should choose shared_ptr.

        If your program does not require multiple pointers to the same object, you can use unique_ptr.

        In short, when choosing, give priority to using unique_ptr. If unique_ptr cannot meet the needs, consider using shared_ptr.

Guess you like

Origin blog.csdn.net/weixin_39766005/article/details/133273208