About c++ smart Pointers and circular references

  • 2020-05-10 18:34:59
  • OfStack

Introduction to c++ smart Pointers

Because C++ language does not have automatic memory recovery mechanism, the programmer has to manually delete every time the memory of new comes out. For example, the process is too complex, resulting in no delete at last. The exception leads to the premature exit of the program. In this way, c++ introduces smart pointer, which is a kind of application of C++ RAII and can be used for dynamic resource management, which is the management strategy of objects. Smart pointer in < memory > Is defined in the std namespace of the header file. They are critical to RAII, or the acquisition of resources, or initialization programming idioms. The main principle of RAII is to provide ownership of all heap allocation resources, such as dynamically allocated memory or system object handles, stack allocation objects where destructors contain code to remove or release resources, and any associated cleanup code.

c++ smart pointer category

c++ smart pointer mainly includes: unique_ptr,shared_ptr, weak_ptr, among which auto_ptr has been abandoned.

unique_ptr

Only 1 owner of the underlying pointer is allowed. We can move to the new owner (with mobile semantics), but we can't copy or share (that is, we can't get two unique_ptr pointing to the same object). Replace the deprecated auto_ptr. Compared to boost::scoped_ptr. unique_ptr small and efficient; The size is equivalent to a pointer and rvalue references are supported for quick insertion and retrieval of STL collections. The header file: < memory > .

Using unique_ptr, the following functions can be achieved:

1. Provide exception security for dynamically requested memory.
2. Pass ownership of the dynamically requested memory to a function.
3, from a function to return the dynamic application memory ownership.
Save the pointer in the container.
5. All functions that auto_ptr should have (but cannot be implemented in C++ 03).

The following code is shown:


class A;
//  If an exception is thrown during program execution, unique_ptr It releases the object it's pointing to 
//  The traditional new  Are not 
unique_ptr<A> fun1()
{
 unique_ptr p(new A);
 //do something
 return p;
}

void fun2()
{  // unique_ptr Have mobile semantics 
 unique_ptr<A> p = f();//  Use the move constructor 
 // do something
}//  When the function exits, p And the object to which it points are deleted and released  shared_ptr 


Smart Pointers for reference counting. shared_ptr is implemented based on the "reference counting" model. Multiple shared_ptr can point to the same dynamic object, and a Shared reference counter is maintained to record the number of instances of shared_ptr referencing the same object. When the last shared_ptr that points to a dynamic object is destroyed, the object it refers to is automatically destroyed (via the delete operator). The default capability of shared_ptr is to manage dynamic memory, but it supports custom Deleter for personalized resource release actions. The header file: < memory > .

Basic operations: shared_ptr creation, copy, binding object change (reset), shared_ptr destruction (manually assigned to nullptr or out of scope), deleter specification, and so on.

There are two ways to create shared_ptr,

1, using the function make_shared(which calls the constructor of the dynamic object based on the parameters passed);

2, using the constructor (created from native pointer, unique_ptr, another shared_ptr)

shared_ptr < int > p1 = make_shared < int > (1); // through the make_shared function

shared_ptr < int > p2 (new int (2)); In addition, if the smart pointer is "empty", that is, it does not point to any object, then it is false; otherwise, it is true, which can be used as a conditional judgment. There are two ways to specify deleter, 1 when shared_ptr is constructed, and 2 when reset is used. Can be overloaded by operator- > , operator *, and other auxiliary operations such as unique(), use_count(), get(), and other member methods.

weak_ptr

Special case smart pointer used in conjunction with shared_ptr. weak_ptr provides access to objects belonging to one or more instances of shared_ptr, but does not participate in reference counting. Use this instance if you want to observe an object but do not need it to remain active. In some cases, you need to break the circular references between instances of shared_ptr. The header file: < memory > .

The usage of weak_ptr is as follows:

weak_ptr is used in conjunction with shared_ptr and does not affect the life cycle of the dynamic object, that is, its existence does not affect the reference counter of the object. weak_ptr does not overload operator- > And the operator * operator, so objects cannot be used directly through weak_ptr. The expired() and lock() member functions are provided, the former to determine whether the object to which weak_ptr points has been destroyed, and the latter to return the shared_ptr smart pointer to the object to which it refers (to return "empty" shared_ptr when the object is destroyed). Circular reference scenarios: circular references between parent and child nodes in a 2 fork tree, circular references between containers and elements, and so on.

A circular reference to a smart pointer

The problem of circular reference can be understood by referring to the problem on this link. "circular reference" simply means that two objects pointing to each other with an shared_ptr member variable will cause circular reference. Cause the reference count to fail. Here's a code to illustrate circular references:


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

class B;
class A
{
public://  In order to save 1 Some steps here   The data member is also declared as public
 //weak_ptr<B> pb;
 shared_ptr<B> pb;
 void doSomthing()
 {
// if(pb.lock())
// {
//
// }
 }

 ~A()
 {
 cout << "kill A\n";
 }
};

class B
{
public:
 //weak_ptr<A> pa;
 shared_ptr<A> pa;
 ~B()
 {
 cout <<"kill B\n";
 }
};

int main(int argc, char** argv)
{
 shared_ptr<A> sa(new A());
 shared_ptr<B> sb(new B());
 if(sa && sb)
 {
 sa->pb=sb;
 sb->pa=sa;
 }
 cout<<"sa use count:"<<sa.use_count()<<endl;
 return 0;
}

The result of the above code is: sa use count:2, notice that sa and sb are not released at this time, resulting in a memory leak problem!!

That is, A has an internal point to B, B has an internal point to A, so for A, B must be destructed after A, B must be destructed after B, A must be destructed after B, this is circular reference problem, violation of the convention, leading to memory leak.

1 generally speaking, there are three possible ways to remove this circular reference (see below) :

1. You need to manually break the circular reference to release the object when only the last reference is left.

2. When the lifetime of A exceeds that of B, B USES a normal pointer to A instead.

3. Break this circular reference with a smart pointer to a weak reference.

Although all three methods work, both method 1 and method 2 require manual control by the programmer, which is cumbersome and error-prone. We use the third method as usual: the smart pointer weak_ptr for weak references.

Strong and weak references

1 strong reference this reference exists when the referenced object is alive (that is, if there is at least one strong reference, the object cannot be released). share_ptr is a strong reference. In contrast, weak references do not always exist when the referenced object is alive. Just a reference when it exists. Weak references do not modify the object's reference count, which means that the weak reference does not manage the object's memory and is functionally similar to a normal pointer. One big difference, however, is that weak references can detect whether the managed object has been freed, thus avoiding illegal memory access.

Use weak_ptr to break the circular reference

The code is as follows:


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

class B;
class A
{
public://  In order to save 1 Some steps here   The data member is also declared as public
 weak_ptr<B> pb;
 //shared_ptr<B> pb;
 void doSomthing()
 {
 if(pb.lock())
 {

 }
 }

 ~A()
 {
 cout << "kill A\n";
 }
};

class B
{
public:
 //weak_ptr<A> pa;
 shared_ptr<A> pa;
 ~B()
 {
 cout <<"kill B\n";
 }
};

int main(int argc, char** argv)
{
 shared_ptr<A> sa(new A());
 shared_ptr<B> sb(new B());
 if(sa && sb)
 {
 sa->pb=sb;
 sb->pa=sa;
 }
 cout<<"sb use count:"<<sb.use_count()<<endl;
 return 0;
}


Related articles: