C++ smart pointer

Pointers are very important in c++, but their use brings up a number of problems: having Pointers have the same life cycle as the object to which they refer, especially when there are multiple Pointers to the same object, can be difficult. When there are multiple Pointers to the same object, when one of the Pointers is destroyed, other Pointers can not appear null dangling pointer phenomenon, nor can the object be destroyed many times; When the last pointer is destroyed, the object must be destroyed at the same time without causing resource leakage.

A common way to avoid the above problem is to use a Smart pointer. When it refers to the last pointer of an object, the object it points to is also destroyed when it is destroyed. The C++11 standard library provides two types of smart Pointers:

  1. Shared_ptr Smart pointer to shared ownership. This class pointer allows multiple Pointers to the same object, which is destroyed at the same time as the last pointer is destroyed.
  2. Unique_ptr Exclusive owned smart pointer. This class of Pointers ensures that only one smart pointer points to an object at a time and can be handed over ownership, which is especially useful for avoiding resource leakage.

1.shared_ptr

Multiple shared_Ptr can share the same object, and when the last owner of the object destroys it, it is responsible for destroying the object and cleaning up all the resources associated with the object. The goal of shared_PTR is to automatically release the resources associated with the object when the object it points to is no longer needed.

From the use of

Shared_ptr can be used just like any other pointer and can be assigned, copied, compared, etc. Shared_ptr is defined in the memory header file.

#include <iostream> #include <string> #include <vector> #include <memory> using namespace std; int main() { shared_ptr<string> pNico(new string("nico")); shared_ptr<string> pJutta(new string("jutta")); PNico = make_shared<string>("nico"); shared_ptr<string> pNico = make_shared<string>("nico"); (*pNico)[0] = 'N'; pJutta->replace(0, 1, "J"); vector<shared_ptr<string>> whoMadeCoffee; whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pNico); whoMadeCoffee.push_back(pJutta); whoMadeCoffee.push_back(pNico); for (auto ptr : whoMadeCoffee) { cout << *ptr << " "; } cout << endl; *pNico = "Nicolai"; for (auto ptr:whoMadeCoffee) { cout << *ptr << " "; } cout << endl; cout << "use_count:" << whoMadeCoffee[0].use_count() << endl; //use_count() counts the number of shared_ptr Pointers to the current object. return 0; }Copy the code

When defining and initializing a shared_ptr pointer, it is recommended to use make_shared because it is safer and uses a single allocation. The alternative is to define a shared_ptr pointer and then assign a new pointer to it, but you must use the reset() method.

shared_ptr<string> pNico; pNico = new string("nico"); PNico. Reset (new String ("nico"));Copy the code

Custom deleter

When you initialize the shared_ptr pointer, you can also define a delete function for it as follows:

shared_ptr<string> pNico(new string("nico"), [](string *p) {
    cout << "delete " << *p << endl;
    delete p;
});
Copy the code

By default, the default delete function called by shared_ptr is delete, not delete[], which means that when shared_ptr frees an object, it is valid only for a single object, but not for an array, and a custom delete function must be used. Or use the helper function provided for unique_ptr as a deleter.

In addition, when an object is destructed when its last shared_ptr no longer refers to the object, it must specify its own destructor strategy, that is, define a custom DELETE function, when the destructor does more than just free memory.

2.weak_ptr

Weak_ptr pointer allows ** to share an object without owning ** the object. When the last shared_ptr of the object no longer points to the object, any Weak_ptr pointer is automatically null. Thus, Weak_ptr only provides constructors that accept a shared_ptr in addition to the default and copy constructors. For the object to which weak_ptr points, * and -> operation functions cannot be used, and a shared_ptr must be set up to point to the object. This is reasonable design for the following reasons:

  1. Setting up a shared_ptr pointer in addition to a Weak_ptr pointer can therefore check whether a corresponding object exists. If not, the operation throws an exception or establishes an empty shared_ptr pointer (which behavior actually depends on the operation being performed).
  2. The shared_ptr pointer cannot be released while the pointed object is being processed.

So a Weak_ptr pointer provides only a small amount of operations, just enough to create, copy, and assign a Weak_ptr pointer and convert it to a shared_ptr pointer, or check if it points to an object.

The use of weak_ptr

The following example uses a Weak_ptr pointer to avoid circumferential references due to the use of shared_ptr, so that all shared_Ptr Pointers cannot be released properly.

class Person
{
public:
    string name;
    shared_ptr<Person> mother;
    shared_ptr<Person> father;
    vector<weak_ptr<Person>> kids;
    Person(const string &n, shared_ptr<Person> m = nullptr,
        shared_ptr<Person> f = nullptr) :
        name(n), mother(m), father(f)
    {};
    ~Person() {
        cout << "delete " << name << endl;
    };

private:

};

shared_ptr<Person> initFamily(const string& name) {
    shared_ptr<Person> mom(new Person(name + "'s mom"));
    shared_ptr<Person> dad(new Person(name + "'s dad"));
    shared_ptr<Person> kid(new Person(name, mom, dad));
    mom->kids.push_back(kid);
    dad->kids.push_back(kid);
    return kid;
}


int main()
{
    shared_ptr<Person> p = initFamily("nico");
    cout << "nico's family exists" << endl;
    cout << "- nico is shared " << p.use_count() << " times" << endl;
    cout << "- name of 1st kid of nico's mom: " << p->mother->kids[0].lock()->name << endl;

    p = initFamily("jim");
    cout << "jim's family exists" << endl;

    system("pause");
    return 0;
}
Copy the code

When a Weak_ptr pointer is used to access an object, a lock() method must be added to the formula of a Weak_ptr pointer, which generates a new shared_ptr pointer to the object. When weak_ptr points to an object that has been freed, the lock() operation also produces an empty shared_ptr pointer, resulting in ambiguous behavior when performing * or -> operations. There are several ways to judge whether the object that weak_ptr points to still exists as follows:

  1. Call the expired() method, which returns true if the object to which the Weak_ptr points does not exist, which is the same as checking if use_count() is 0, but is faster.

  2. The shared_ptr constructor is used to explicitly convert a Weak_Ptr object to a shared_Ptr object. If the object to which a Weak_Ptr points does not exist, the constructor throws a BAD_Weak_ptr exception.

  3. The use_count() method is called to ask for the number of owners of the corresponding object, returning 0 to indicate that there are no valid objects, but this method is not efficient.

     try
     {
         shared_ptr<string> sp(new string("hi"));
         weak_ptr<string> wp = sp;
         sp.reset();
         cout << wp.use_count() << endl;
         cout << boolalpha << wp.expired() << endl;
         shared_ptr<string> p(wp);
     }
     catch (const exception& e)
     {
         cerr << "exception: " << e.what() << endl;
     }
    Copy the code

Shared point misuse

Shared_ptr enhances program security, but since the corresponding resources of an object are often automatically freed, problems can occur when the object is no longer in use.

  1. Circular dependencies of the shared_ptr pointer, which will result in a null pointer, i.e. the resource to which the pointer points in a circular dependency will not be freed properly.
  2. You must ensure that the object is only owned by a set of shareD_ptr. When an object is owned by multiple groups of shared_Ptr objects, multiple groups of shared_PTR objects have the right to release the corresponding resources, and the corresponding resources can be released repeatedly, causing problems. To avoid this situation, create the corresponding smart pointer directly when creating the object and its corresponding resource.

unique_ptr

Unique_ptr is a smart pointer that helps avoid resource leaks when an exception occurs. In general, unique_ptr implements the concept of ** exclusive ** ownership, which ensures that an object and its owned resources are owned by only one pointer at a time. Once the owner is destroyed or becomes empty, or starts owning a new object, the previously owned object is destroyed and any corresponding resources are released.

Unique_ptr is used in much the same way as normal Pointers, using * to point to objects and -> to query object members, but it does not provide pointer arithmetic operations (such as ++ operations). In addition, unique_ptr does not allow an ordinary pointer as an initial value and must initialize the unique_ptr pointer directly, as follows:

std::unique_ptr<int> up = new int; // error, cannot initialize STD ::unique_ptr<int> up(new int); / / rightCopy the code

The unique_ptr pointer can be null, it can point to nothing, it can point to nullPtr or it can be null by calling reset(). When we call the release() method of the unique_ptr pointer, we get the object owned by the unique_ptr pointer and give up ownership, as follows:

std::unique_ptr<std::string> up(new std::string("nico"));
std::string* sp = up.release();
Copy the code

Because the semantics provided by unique_ptr are * exclusive ownership *, you cannot perform normal copy and assignment operations on unique_ptr, but you can use move semantics.

The purpose of the unique_ptr

  1. The transfer of unique_ptr ownership points to one use: functions can use them to transfer ownership to other functions, that is, they can use unique_ptr as an input or return value of the function without worrying about resource leakage.

  2. Unique_ptr can be used as a member of a class to avoid resource leakage. The destructor can only be called after all construction actions have been completed. If an exception occurs during the execution of the constructor, the destructor will not be called, and the allocated resources in the constructor will not be recycled, resulting in resource leakage. Using unique_ptr instead of a normal pointer can avoid this situation. And after using unique_ptr instead of a normal pointer, you can skip the destructor and use the default destructor.

  3. Array processing. The c++ standard library provides a specialized version of unique_ptr to handle arrays. If unique_ptr loses ownership of the object it points to, it will call delete[] on the object. This is declared as follows:

     std::unique_ptr<std::string[]> up(new string[10]);
     std::cout<< up[0] <<std::endl;
    Copy the code

In this version, instead of providing * and -> operations, the unique_ptr pointer provides the [] subscript operator to access an object in an array.

Reference [] (https://blog.csdn.net/zy19940906/article/details/50470087), the principle of smart pointer in c + + 11, use and implementation