Using shared_for C++ smart pointer PTR weak_ PTR unique_ PTR

Posted by lewel on Mon, 03 Jan 2022 18:34:18 +0100

shared_ptr

Heap memory is used very frequently in C++ programming. The application and release of heap memory are managed by the programmer himself. Programmers themselves
Managing heap memory improves program efficiency, but overall heap memory management is cumbersome, with the introduction of smart pointers in C++11
Concepts to facilitate heap memory management. Using a normal pointer can easily cause heap memory leaks (forgot to release), secondary releases, and program exceptions.
With smart pointers, heap memory can be better managed.
Four smart pointers in C++: auto_ Ptr, unique_ Ptr, shared_ Ptr, weak_ The last three of PTR support are C++11.
And the first one has been deprecated by C++11.

Principle of Smart Pointer:

The idea of smart pointers is to manage another object by one object. When a function is de-stacked, the destructor of the object is called when it is released because memory on the stack is freed. A smart pointer is a class that manages other objects, and automatically destructs the objects it manages when the conditions are met. At the same time, the operator that overloads the pointer implements the operation of the pointer.

shared_ptr shared smart pointer

std::shared_ptr uses reference counts, each shared_ Copies of PTR all point to the same memory. Last shared_ptr analysis
Memory is only released when the structure is constructed.
shared_ptr shares managed objects and can have multiple shared_at a time PTR owns the ownership of the object, when last
Shared_ When the PTR object is destroyed, the managed object is destroyed automatically.
Simply put, shared_ The PTR implementation consists of two parts

  1. A bare pointer to the object created on the heap, raw_ptr, used to point to actual objects
  2. A management object that points to an internal hidden, shared object. share_count_object, used for reference counting, managed resources are freed when the count is 0, and multithreading is thread-safe.

Initialization

First let's start with the header file of the next header file smart pointer #include <memory>

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

class A{
public:
   A(){
       std::cout <<"A()" << endl;
   }

   ~A(){
       std::cout <<"~A()" <<endl;
   }
};

void DeleteIntPtr(int *p) {
   cout << "call DeleteIntPtr" << endl;
   delete p;
}
void shard_init()
{
 //  std::shared_ptr<int> p1 = new int(1); //A compiler error
     std::shared_ptr<int> p2(new int(1));  //Open up an int space on the heap and assign a value of 1 to the smart pointer
     std::shared_ptr<int> p3 = p2;		//p3 and p2 both point to the space just opened up
     std::shared_ptr<int> p4 = make_shared<int>(10); //Recommended initialization methods
     std::shared_ptr<int> p5;	 //raw pointers
     auto p6 = p5;
     auto p7 = make_shared<int>(1000); //This is the most recommended and concise way to write
   
     std::shared_ptr<int> p8(new int(10000), DeleteIntPtr); // shared_ptr can customize how memory is freed so that operations such as closing files can also be done.
     std::shared_ptr<int> p9(new int(100000), [](int* p){ //Memory Free Using Anonymous Functions
         cout << "delete p9"<<endl;
         delete p;
     });

     std::shared_ptr<A> p10(new A[10], [](A* p){  //An array has been opened up which contains the pointer to A or not
         cout << "delete p10" <<endl;
         delete []p;
     });

     std::shared_ptr<int> p11(new int[10], [](int* p){
         cout << "delete p10" <<endl;
         delete []p;
     });

     p11.get()[10] = 100;
}

To summarize the above:

  1. shared_ptr can be copied, for each shared_ptr points to a block of memory at the same time.
  2. shared_ptr can customize memory release methods, use destructors if not specified, and manage resources flexibly
  3. Recommended auto p = make_shared();

Common methods

void shard_operation()
{
    auto p1 = make_shared<A>();
    //View the number of references
    cout << p1.use_count() << endl;
    auto p2 = p1;
    cout << p1.use_count() << endl;
    //release
    p1.reset();
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
	
	//Getting a bare pointer, you can get a bare pointer with a smart pointer, but this is a dangerous operation and is released accidentally
	//This bare pointer, then the program will collapse to show you.
    A* ptr = p1.get();
    // delete ptr;
    //Getting a bare pointer is typically done when the parameter to some function is the original pointer
    // void test(A* ptr);
}
  1. use_count() can get how many reference counts the smart pointer has.
  2. reset() can subtract the pointer's resource reference count by one, and this interface has many overloaded functions, or it can reset resources to be managed
  3. get() a bare pointer to a resource

Issues to Note

  1. Do not initialize multiple shared_s with one original pointer PTR
int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);

If running directly results in two releases, both p1 and p2 are released, thus double free

  1. Do not create shared_in the arguments to the function anymore PTR
function(shared_ptr<int>(new int), g()); //Defective

Because the order of calculation of function parameters in C++ may vary according to different compiler conventions, typically from right to left, but also from left to right, the possible procedure is to first new int and then call g(), if exactly g() has an exception and shared_ If the PTR has not been created yet, the int memory leaks. The correct way to write it is to create a smart pointer first, as follows:

shared_ptr<int> p(new int);
function(p, g());
  1. By shared_from_this() returns the this pointer. Do not use this pointer as shared_ptr returns because this pointer is essentially a bare pointer
    See the following code:
class B
{
public:
    shared_ptr<B> GetSelf()
    {
        return shared_ptr<B>(this); // Don't do this
    }
    ~B()
    {
        cout << "Deconstruction B" << endl;
    }
};

class C :public std::enable_shared_from_this<C>
{
public:
    shared_ptr<C> GetSelf()
    {
        return shared_from_this();
    }
    ~C()
    {
        cout << "Deconstruction B" << endl;
    }
};

void shared_ptr_waring(){
   shared_ptr<B> sp1(new B);
   //shared_ptr<B> sp2 = sp1->GetSelf();
   cout << sp1.use_count() << endl;
   //cout << sp2.use_count() << endl;


   shared_ptr<C> spc1(new C);
   shared_ptr<C> spc2 = spc1->GetSelf();
   cout << spc1.use_count() << endl; //2
   cout << spc1.use_count() << endl; //2
}

First in

   shared_ptr<B> sp1(new B);
   shared_ptr<B> sp2 = sp1->GetSelf();
   cout << sp1.use_count() << endl;
   cout << sp2.use_count() << endl;

If you release an annotation, there are actually two smart pointers sp1 and sp2 pointing to the same block of memory, which results in a duplicate release.
Inherit enable_if you do not want to return the function Shared_ From_ This <T>

The code pointers spc1 and spc2 above are normal if both references are printed out as 2

  1. shared_ptr cross-reference issues
class D;
class E;

class D{
public:
    ~D(){
        cout << "D" <<endl;
    }

    shared_ptr<E> pe;
};

class E{
public:

    ~E(){
        cout << "E" <<endl;
    }
    shared_ptr<D> pd;
};

void circular_reference()
{
    shared_ptr<D> p1(new D);
    shared_ptr<E> p2(new E);

    p1->pe = p2;
    p2->pd = p1;

    cout << p1.use_count() <<endl; //Print 2
    cout << p2.use_count() <<endl; //Print 2
}

The above should go out of circular_ When referencing the scope, D and E that were new er were not released.
It is well understood that p1 and p2 references within the function body are reduced, but there are reference counts for member variables in the class, which will result in reference counts not reaching zero and memory will not be freed.
The above questions can be used: ````std::weak_ptr``Processing

Weak_ PTR (Smart Pointer with Weak References)

weak_ptr is an intelligent pointer that does not control the life cycle of an object and points to a shared_ Object managed by ptr. It is the strongly referenced shared_that manages the object's memory Ptr, weak_ptr only provides access to managed objects. weak_ptr is designed to work with shared_ A smart pointer introduced by PTR to assist shared_ptr works, it can only work from a shared_ptr or another weak_ptr object construction, its construction and destruction will not cause an increase or decrease in the reference count. weak_ptr is used to solve shared_ Deadlock when PTR refers to each other, if two shared_ptr refers to each other, then the reference count of these two pointers can never drop to zero, and the resource will never be released. It is a weak reference to an object and does not increase the object's reference count, and shared_ PTRs can be converted to each other, shared_ptr can be assigned directly to it, and it can get shared_by calling the lock function Ptr.
weak_ptr does not have overloaded operators * and -> because it does not share pointers and cannot manipulate resources, primarily through shared_ptr gets monitoring rights for resources, its construction does not increase the reference count, its destruction does not decrease the reference count, it simply monitors shared_as a bystander Does the resource managed in PTR exist? weak_ptr can also return this pointer and resolve circular references.

The saying is:

  1. weak_ptr is used to assist shared_ptr solves the problem of circular references
  2. shared_ptr is just a monitor, it doesn't manage resources
  3. shared_ptr can drink shared_ptr for conversion

Weak_ Basic usage of PTR

  1. By use_count() method gets the reference count of the current observation resource
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl;

The print result above is 1, which, in other words, is a monitor

  1. expired() method to determine whether the observed resource has been released
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
	cout << "weak_ptr invalid,Resource released";
else
	cout << "weak_ptr effective";

Print: weak_ptr is valid;

  1. Monitored shared_can be obtained by the lock() method Ptr, if monitored is invalid, an empty shared_will be returned Ptr.
std::weak_ptr<int> gw;
void f()
{
	if(gw.expired()) {
		cout << "gw invalid,Resource released";
	}
	else {
		auto spt = gw.lock();
		cout << "gw effective, *spt = " << *spt << endl;
	}
}
int main()
{
	{
		auto sp = atd::make_shared<int>(42);
		gw = sp;
		f();
	}
	f();
	return 0;
}

Previously if you wanted to pass shared_ptr returns this, causing problems to be released twice by inheriting std::enable_ Shared_ From_ This <T> solution actually encapsulates a weak_inside Ptr, returning shared_from_this(); Create a shared_with lock Ptr. Then associate the two pointers in spc1 (above) and spc2 (above). It is important to note that if you inherit the class, create it directly using new, and then call the method, you will get an error.

  1. weak_ptr Solves Circular References
    In shared_ The PTR section mentions the problem of a smart pointer circular reference, because a circular reference to a smart pointer can cause a memory leak and can be passed through weak_ptr solves this problem by changing any member variable of D or E to weak_ptr
class D;
class E;

class D{
public:
    ~D(){
        cout << "D" <<endl;
    }

    std::weak_ptr<E> pe;
   // shared_ptr<E> pe;
};

class E{
public:

    ~E(){
        cout << "E" <<endl;
    }
    shared_ptr<D> pd;
};

void circular_reference()
{
    shared_ptr<D> p1(new D);
    shared_ptr<E> p2(new E);

    p1->pe = p2; //p1->pe : E.pe(weak_ptr)
    p2->pd = p1; //p2->pd : shared_ptr<D>

    cout << p1.use_count() <<endl; 2
    cout << p2.use_count() <<endl; 1
}

Print information:
2
1
E
D
weak_ptr does not take up reference counts, so p2.use_count() is 1. When destructing, the resource pointed to by P2 is destructed first, then p2. The PD reference count is reduced by 1, and then p1 is destructed to properly release resources and solve the problem of circular references.

unique_ptr

  1. unique_ptr is an exclusive smart pointer, it does not allow other smart pointers to share their own internal pointer, it does not allow assigning a unique_ptr is assigned to another unique_ptr, but std::move();
unique_ptr<T> my_ptr(new T);
unique_ptr<T> my_other_ptr = my_ptr;
unique_ptr<T> my_other_ptr = std::move(my_ptr);

The second line will make an error.
You can use the third line.

  1. unique_ptr can point to an array with the following code
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
  1. Unique_ The type of deletor needs to be determined when PTR is deleted again, so it can't be like shared_ Directly specify the deletor as PTR does, so you can write
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;});

About shared_ptr and unique_ The usage scenario of PTR is to be selected according to practical application requirements. If you want only one smart pointer tube
Use unique_for managing resources or arrays Ptr, share_if you want multiple smart pointers to manage the same resource Ptr.

Above are the three current ways to use the smart pointer. Later in the actual battle, if there is anything to pay attention to, update it later.
It's not early, good night and good night.

Topics: C++ Back-end