C++ smart pointers

1. Why smart pointers?

Let's first look at a simple example:

int remodel(string & str, bool flag)
{
    string * ps = new string(str);
    if (flag)
        return -1;
    str = *ps; 
    delete ps;
    return 0;
}

When flag=true, delete will not be executed, thus causing a memory leak.

How to avoid this problem? Some people will say that this is not easy, just add delete ps; before return -1; Yes, you should. The problem is that many people forget to add the delete statement in the appropriate place (even the last delete statement in the above code will be forgotten by many people), if you have to deal with a huge project Do a review to see if there is such a potential memory leak, and that would be a disaster!

At this point we will think: when a function such as remodel terminates (whether it terminates normally or due to an exception), the local variables will be automatically deleted from the stack memory - so the memory occupied by the pointer ps will be freed, if ps The memory pointed to is also automatically freed, which would be great.

We know that the destructor has this function. If ps has a destructor, that destructor will automatically free the memory it points to when ps expires. But the problem with ps is that it's just a regular pointer, not a pointer to a class object with a destructor. If it points to an object, you can have its destructor delete the pointed-to memory when the object expires .

This is the reason for the design of the smart pointers auto_ptr, unique_ptr and shared_ptr.
A simple summary is: encapsulate the basic type pointer as a class object pointer (this class must be a template to meet the needs of different basic types), and write a delete statement in the destructor to delete the memory space pointed to by the pointer .

Use auto_ptr to modify the result of this function:

# include <memory>
using namespace std;
int remodel (string & str, bool flag)
{
    auto_ptr<string> ps (new string(str));
    if (flag)
        return -1
    str = *ps; 
    // delete ps; 不再需要
    return 0;
}

The modification requires the following three steps:
• Include the header file memory (the header file where the smart pointer is located);
• Replace the pointer to the string with the smart pointer object pointing to the string;
• Delete the delete statement.

2. A brief introduction to C++ smart pointers

STL provides us with a total of four smart pointers: auto_ptr, unique_ptr, shared_ptr and weak_ptr.
auto_ptr is the solution provided by C++98, C+11 has abandoned it and provides two other solutions. However, while auto_ptr was deprecated, it has been used for years: meanwhile, if your compiler doesn't support the other two solutions, auto_ptr will be the only option.
The following are examples of the use of auto_ptr, unique_ptr, shared_ptr three smart pointers:

#include <iostream>
#include <string>
#include <memory>

class report
{
private:
    std::string str;
public:
 report(const std::string s) : str(s) {
  std::cout << "Object created.\n";
 }
 ~report() {
  std::cout << "Object deleted.\n";
 }
 void comment() const {
  std::cout << str << "\n";
 }
};

int main() {
 {
  std::auto_ptr<report> ps(new report("using auto ptr"));
  ps->comment();
 }

 {
  std::shared_ptr<report> ps(new report("using shared ptr"));
  ps->comment();
 }

 {
  std::unique_ptr<report> ps(new report("using unique ptr"));
  ps->comment();
 }
 return 0;
}

Note: All smart pointer classes have an explicit constructor that takes a pointer as a parameter.
So the pointer cannot be automatically converted to a smart pointer object, it must be called explicitly. The call method must be:

shared_ptr<T> ps(new T())
**不能用以下形式:**
T a;
shared_ptr<T> ps(&a);

Because the variable a is stored in stack memory, when ps expires, the program will use the delete operator for non-heap memory, which is wrong.

3. Why abandon auto_ptr?

Take a look at the following example:

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;

What does the assignment statement above do? If ps and vocation are regular pointers, both pointers will point to the same string object. This is not acceptable because the program will try to delete the same object twice - once when the ps expires and once when the vocation expires. There are several ways to avoid this problem:
• Define an assignment operator that performs a deep copy. In this way, the two pointers will point to different objects, and one of the objects is a copy of the other object. The disadvantage is that space is wasted, so no smart pointer adopts this scheme.

Establish the concept of ownership . For a particular object, only one smart pointer can be owned, so that only the constructor that owns the object's smart pointer will delete the object. Then let the assignment operation transfer ownership. This is the policy used for auto_ptr and uniqiie_ptr, but the policy for unique_ptr is stricter.

Create smarter pointers that keep track of the number of smart pointers that refer to a particular object . This is called reference counting. For example, when an assignment is made, the count will be incremented by 1, and when the pointer expires, the count will be decremented by 1. delete is called only when it is reduced to 0. This is the strategy adopted by shared_ptr.

As in the above example, when the auto_ptr smart pointer is used, when the vocaticn = ps operation is performed, the ownership of the smart pointer is transferred from ps to vocaticn, and ps has become a null pointer. At this time, if data is taken from the ps pointer, the program will will crash .

But here, if auto_ptr is replaced by shared_ptr or unique_ptr, the program will not crash. The reasons are as follows:
• It works normally when shared_ptr is used, because shared_ptr uses reference counting, and ps and vocaticn both point to the same piece of memory. Determine the size of the reference count value so there is no error in deleting an object multiple times.
Compilation error when using unique_ptr . Like auto_ptr, unique_ptr also adopts the ownership model, but when using unique_ptr, the program will not wait until the runtime crashes, and the compiler will get an error due to the following line of code. This is also the point that unique_ptr is better than auto_ptr .

That's why auto_ptr is abandoned: to avoid potential memory corruption problems .

4. The difference between shared_ptr and weak_ptr

A weak_ptr is a smart pointer that does not control the lifetime of an object, it points to an object managed by a shared_ptr.
The memory management of the object is the shared_ptr with strong reference. weak_ptr only provides an access method to the management object.
Use weak_ptr to solve the problem that shared_ptr cannot release resources due to circular reference.

As an example of a doubly linked list, the circular reference caused by shared_ptr is as follows:

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

struct Node  
{  
    shared_ptr<Node> _pre;  
    shared_ptr<Node> _next;  

    ~Node()  
    {  
        cout << "~Node():" << this << endl;  
    }  
    int data;  
};  

void FunTest()  
{  
    shared_ptr<Node> Node1(new Node);  
    shared_ptr<Node> Node2(new Node);  
    Node1->_next = Node2;  
    Node2->_pre = Node1;  

    cout << "Node1.use_count:"<<Node1.use_count() << endl; 
    cout << "Node2.use_count:"<< Node2.use_count() << endl;
}  

int main()  
{  
    FunTest();  
    system("pause");  
    return 0;  
}

The reference count values ​​of Node1 and Node2 are both 2, which can never become 0 and will not be released.

For the above-mentioned result that the space cannot be released due to the reference count and the number of objects that manage the space, it is a circular reference. The solution is: use weakly referenced smart pointers to break this circular reference.

A strong reference means that while the referenced object is alive, the reference also exists (that is, as long as there is at least one strong reference, the object will not and cannot be released). share_ptr is a strong reference .

Weak references do not modify the object's reference count , which means that weak references do not manage the object's memory. It is similar in function to ordinary pointers, however, a big difference is that weak references can detect whether the managed object has been freed, thus avoiding illegal memory access.

The purpose of weak_ptr design is to cooperate with shared_ptr to introduce a smart pointer to assist shared_ptr work. It can only be constructed from a shared_ptr or another weak_ptr object, and its construction and destruction will not cause the reference count to increase or decrease.

The member functions of weak_ptr are:
weak_ptr() :
There is no overloading * and ->, but you can use lock to obtain an available shared_ptr object. Note that weak_ptr needs to be checked for validity before use.
expired():
used to detect managed Whether the object has been released, if it has been released, return true; otherwise return false.
lock():
used to acquire a strong reference to the managed object (shared_ptr). If expired is true, return an empty shared_ptr; otherwise return a shared_ptr, Its internal object points to the same as weak_ptr.
use_count():
returns the reference count of the object shared with shared_ptr.
reset():
empty weak_ptr.
weak_ptr supports copy or assignment, but does not affect the count of the corresponding shared_ptr internal object.
Example of use:

#include <iostream>
#include <memory>

std::weak_ptr<int> gw;//全局变量

void f()
{
    if (auto spt = gw.lock()) // 使用之前必须复制到 shared_ptr
    {
        std::cout << *spt << "\n";
    }
    else
    {
        std::cout << "gw is expired\n";
    }
}

int main()
{
    {
        auto sp = std::make_shared<int>(42);
         gw = sp;
         f();
    }

    f();
}

Output:
42
gw is expired

5. How to choose a smart pointer?

After mastering these kinds of smart pointers, you may think about another question: which smart pointer should be used in practical applications?
A few usage guidelines are given below.
(1) If the program wants to use multiple pointers to the same object, shared_ptr should be selected. Such cases include:
STL containers contain pointers. Many STL algorithms support copy and assignment operations, which are available for shared_ptr, but not for unique_ptr (the compiler issues a warning) and auto_ptr (the behavior is undefined). If your compiler does not provide shared_ptr, use the shared_ptr provided by the Boost library.

(2) If the program does not require multiple pointers to the same object, unique_ptr can be used. If a function allocates memory using new and returns a pointer to that memory, it is a good idea to declare its return type as unique_ptr. This way, ownership is transferred to the unique_ptr that accepts the return value, and that smart pointer will be responsible for calling delete. A unique_ptr can be stored into an STL container there, as long as an algorithm that copies or assigns a unique_ptr to another (such as sort() ) is not called.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324749010&siteId=291194637
Recommended