Introduction to C + + smart pointer

Keywords: C++ boost

Write before:

In many scenarios, we need to apply for memory dynamically, but the use of dynamic memory is prone to problems, because it is extremely difficult to ensure that the memory is released at the right time. Sometimes we forget to release memory. This situation will lead to memory leakage, which is difficult to find, because the phenomenon will not appear immediately, and releasing a piece of memory for many times will easily lead to crash.
In order to use dynamic memory more easily and safely, C + + provides us with the concept of smart pointer. It manages objects through pointers. Its behavior is more like pointers, which is mainly responsible for automatically releasing the pointed objects.

Smart pointer header file < memory >

1, Principle of intelligent pointer

When it comes to smart pointers, we have to mention a name called Rall (resource acquisition initialization), which translates to resource acquisition initialization

It is the design concept proposed by Bjarne Stroustrup, the father of C + +. Its core is to bind the life cycle of resources and objects, create objects to obtain resources, destroy objects to release resources. Under the guidance of RAII, C + + promotes the underlying resource management to a higher level of object life cycle management.

Obtain resources during object construction, then control access to resources to keep them valid throughout the object's life cycle, and finally in the process of object deconstruction
When to release resources. In this way, we actually trust the responsibility of managing a resource to an object (hear me, trust you to your object, and then your object will manage you until you are deconstructed).
This approach has two major benefits:
1. There is no need to explicitly release resources.
2. In this way, the resources required by the object remain effective throughout its life cycle.

Example: RALL thought

1.1 memory forgetting release of dynamic application

    for (int i = 0; i <= 10000000; ++i)  
    {  
        int *ptr = new int[3];  
        ptr[0] = 1;  
        ptr[1] = 2;  
        ptr[2] = 3;  
        //delete ptr;     // Resource not released
    } 

The result is memory leakage (do not imitate dangerous actions)
We all know that the constructor and destructor of a class automatically call its constructor when the object is generated and its destructor at the end of the object's life cycle. If I use such a mechanism to manage my dynamically applied objects, I won't be afraid to forget to release resources any more.
Example:

#include"SmartPtr.h"
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

void Test1()
{
		int *ptr = new int[3];
		SmartPtr<int> sp(ptr);
		ptr[0] = 1;
		ptr[1] = 2;
		ptr[2] = 3;
		//delete ptr;     // Resource not released
}
int main()
{
	Test1();
	cout << "hahhah" << endl;
	system("pause");
	return 0;
}


It can be seen here that the addresses of SP and ptr defined by us are the same, and the contents pointed to by the two pointers are the same. The automatic release of resources is realized through the management of intelligent pointer sp. of course, there is no problem with manually releasing ptr pointer, because the life cycle of both is the same. ptr is released, and SP is also released naturally. There is no problem of illegal memory release for many times.

Such a SmartPtr class can indeed reflect the idea of smart pointer, but the SmartPtr here does not have the behavior of a pointer. We all know that the pointer supports dereference or - > to access the content of the pointed space, so we can overload * and - > again, This is the general implementation principle of intelligent pointer.

2, auto_ptr,unique_ptr ,scoped_ptr ,shared_ptr ,weak_ Introduction to PTR

2.1 auto_ptr

auto_ptr is the smart pointer provided in the C++ 98 library
auto_ The realization principle of PTR: the idea of management power transfer.
The general realization idea of the pointer is as follows:

#include <memory>
class Date
{
public:
 Date() { cout << "Date()" << endl;}
 ~Date(){ cout << "~Date()" << endl;}
 int _year;
 int _month;
 int _day;
};
int main()
{
 auto_ptr<Date> ap(new Date);
 auto_ptr<Date> copy(ap);
 // auto_ptr problem: when the object is copied or assigned, the previous object is suspended
 // So auto_ptr has very serious defects, and it is not recommended to use this kind of smart pointer many times now
 ap->_year = 2018;
 return 0;
}


This is because auto_ptr, the idea of management power transfer, auot_ The general idea of assignment operation in PTR is as follows:

 AutoPtr<T>& operator=(AutoPtr<T>& ap)
 {
 // Check whether you assign a value to yourself
 	if(this != &ap)
 	{
 		// Freeing resources in the current object
 		if(_ptr)
 		delete _ptr;
 	
 		// Transfer ap resources to the current object
 		_ptr = ap._ptr;
 		ap._ptr = NULL;
 }
 	return *this;
 }

We can see that when we access the space pointed to by the released pointer, there will be a crash problem.

2.2 unique_ptr

Because of the lessons learned from the past, C++ 11 began to provide more reliable unique_ptr
As the saying goes, stand up where you fall, unique_ptr is very direct and violent. It can be said to be unique_ptr is auto_ptr 2.0 fix

The specific implementation principles are as follows:

1. Only declare the interface but not implement it. And declare it private, which is the implementation method of C98 version
2.C++ 11 directly deletes it

2.3 scoped_ptr

scoped_ptr is a similar to auto_ptr's smart pointer, which wraps the dynamic objects allocated by the new operator on the heap, can ensure that the dynamically created objects can be deleted correctly at any time.
scoped_ptr also declares the copy constructor and assignment operation as private, prohibits the copy operation of smart pointers, and ensures that the pointers managed by PTR cannot be transferred.
So scoped_ptr and unique_ The implementation principle of PTR is the same.

2.4 shared_ptr

The above operations that directly kill the assignment and copy of the pointer certainly do not meet the needs of some scenarios. Therefore, C + + provides a more reliable and copy supported shared_ptr

shared_ Principle of PTR: multiple shared are implemented by reference counting_ Resources are shared between PTR objects.

  1. shared_ptr maintains a count for each resource to record that the resource is shared by several objects.
  2. When the object is destroyed (that is, the destructor call), it means that it does not use the resource, and the reference count of the object is reduced by one.
  3. If the reference count is 0, it means that you are the last object to use the resource, and you must release the resource;
  4. If it is not 0, it means that other objects besides yourself are using the resource and cannot release the resource. Otherwise, other objects will become wild fingers
    The needle is gone.

General realization idea:

void Release()
 {
 	bool deleteflag = false;
	 // The reference count is reduced by 1. If it is reduced to 0, the resource is released
	 _pMutex.lock();//Ensure thread safety
	 if (--(*_pRefCount) == 0)
	 {
		 delete _ptr;
		 delete _pRefCount;
 		deleteflag = true;
	 }
	 _pMutex.unlock();
 
 	if(deleteflag == true)
 	{
    	delete _pMutex;
    }
 }
private:
 		int* _pRefCount; // Reference count
 		T* _ptr; // Pointer to the managed resource 
 		mutex* _pMutex; // mutex 

shared_ptr ensures the reasonable release of resources by reference counting, and also considers the problem of thread safety. Making multiple threads access at the same time can also ensure thread safety.
But shared_ptr still has a circular reference problem
Circular reference:

  1. The two smart pointer objects node1 and node2 point to two nodes, and the reference count becomes 1. We don't need to delete manually.
  2. Node1_ next points to node2, and node2's_ prev points to node1 and the reference count becomes 2.
  3. node1 and node2 are destructed, and the reference count is reduced to 1, but_ Next also points to the next node. But_ prev also points to the previous node.
  4. That is to say_ next destructs and node2 is released.
  5. That is to say_ When prev destructs, node1 is released.
  6. But_ next is a member of node, and node1 is released_ next will destruct, and node1 is composed of_ prev management_ prev belongs to node2
    Member, so this is called circular reference, and no one will release it.

Like this:

int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	node1->_next = node2;
	node2->_prev = node1;
	//Finally, no one will release it, or it will cause memory leakage
	return 0;

How to solve such a problem?
Next, we will introduce weak_ The concept of PTR

2.5 weak_ptr

weak_ptr enables a smart pointer that does not control the lifetime of the object it points to, which is pointed to by a shared_ptr managed objects. Put a weak_ptr is bound to a shared_ptr, but it will not change shared_ptr reference count, once the last one points to the object's shared_ If PTR is destroyed, the object will be destroyed. Even with weak_ptr points to the object, which will still be released.
Therefore, the creation of this smart pointer requires a shared_ptr to initialize it.
Example:

auto p = make_shared<int>(0);
weak_ptr<int> wp(p);

And shared_ The defect of PTR circular reference can also be solved

struct ListNode
{
	 int _data;
	 weak_ptr<ListNode> _prev;
	 weak_ptr<ListNode> _next;
	 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 	shared_ptr<ListNode> node1(new ListNode);
	 shared_ptr<ListNode> node2(new ListNode);
	 node1->_next = node2;
	 node2->_prev = node1;
	 return 0;
}

Posted by naveenbj on Sun, 05 Sep 2021 10:59:01 -0700