A king's time, I learned the smart pointer in c + + (including code implementation)

Posted by kjelle392 on Sat, 05 Mar 2022 16:50:49 +0100

catalogue

1. What is a memory leak

2. How to avoid memory leakage

3. Why is there a smart pointer

4. Use and principle of intelligent pointer

4.1RAII

4.2 classes designed with RAII idea

4.3 implementation of intelligent pointer

5. Summary

1. What is a memory leak

1. Memory leak: memory leak refers to the situation that the program fails to release the memory that is no longer used due to negligence or error. Memory leakage does not mean the physical disappearance of memory, but the loss of control over a certain section of memory due to design errors after the application allocates it, resulting in a waste of memory. It often refers to heap memory leakage, because the heap is dynamically allocated and controlled by users. If it is not used properly, memory leakage will occur.

When using malloc, calloc, realloc, new, etc. to allocate memory, call the corresponding free or delete to release memory after use, otherwise this memory will cause memory leakage.

2. Harm of memory leakage: long-term running programs have memory leakage, which has a great impact, such as operating system, background services, etc. memory leakage will lead to slower and slower response and finally get stuck.

2. How to avoid memory leakage

1. Good design specifications in the early stage of the project, develop good coding specifications, and remember to release the matched memory space. ps: this ideal state. However, if there is an exception, even if the attention is released, there may still be a problem. You need the next smart pointer to manage it.
2. Use RAII idea or smart pointer to manage resources.
3. Some companies use internally implemented private memory management libraries according to internal specifications. This set of library comes with memory leak detection function options.
4. If something goes wrong, use the memory leak tool to detect it. ps: but many tools are unreliable or expensive.
5. To sum up: memory leaks are very common, and there are two solutions: 1. Pre prevention. Such as smart pointer. 2. Error checking afterwards. Such as leak detector
Tools.

3. Why is there a smart pointer

In c + +, we open up memory on the heap and easily forget to release it. Or in some cases of throwing exceptions, the space on the heap is not released. In order to solve this kind of problem, c + + introduces smart pointers to help us manage memory.

4. Use and principle of intelligent pointer

Smart pointer is proposed to solve the memory leakage caused by dynamic memory allocation and release the same memory space for many times. C++11 is encapsulated in the < memory > header file.

Smart pointers in C++11 include the following three types:

shared_ptr: resources can be shared by multiple pointers. The counting mechanism is used to indicate that resources are shared by several pointers. Through use_count() to view the number of resource owners. You can use unique_ptr,weak_ptr is constructed by calling release() to release the ownership of resources. When the count is reduced to 0, the memory space will be automatically released to avoid memory leakage.
unique_ptr: a smart pointer with exclusive ownership. Resources can only be occupied by one pointer, which cannot be copied, constructed or assigned. However, it is possible to carry out mobile construction and mobile assignment construction (call the move() function), that is, a unique structure_ PTR object assigned to another unique_ PTR object, which can be assigned by this method.
weak_ptr: point to share_ The object pointed to by PTR can be solved by shared_ The circular reference problem caused by PTR.

c++98 provides auto_ptr:auto_ptr is a class template provided by C + + standard library, auto_ptr object points to the dynamic memory created by new through initialization. It is the owner of this memory. A piece of memory cannot be divided into two owners at the same time. When auto_ At the end of the PTR object's life cycle, its destructor will auto_ The dynamic memory owned by PTR object is automatically released. Even if an exception occurs, the dynamic memory can be released through the abnormal stack expansion process. auto_ Array PTR new is not supported.

4.1RAII

 1.RAII (Resource Acquisition Is Initialization) is a simple technology that uses the object life cycle to control program resources (such as memory, file handle, network connection, mutex, etc.).
2. Obtain resources during object construction, then control the access to resources to keep them valid throughout the object's life cycle, and finally release resources during object destruction. In this way, we actually entrust the responsibility of managing a resource to an object. This approach has two major benefits:
2.1. There is no need to explicitly free resources.
2.2. In this way, the resources required by the object remain valid throughout its lifetime.

4.2 classes designed with RAII idea

For classes designed with the idea of RAII, we usually overload * and - > to make this class behave like a pointer.

template<class T>
	class SmartPtr {
		SmartPtr( T* ptr)//Constructor
			:_ptr(ptr)
		{}

		T& operator*() {//Simulate pointer like behavior
			return *_ptr;
		}
		
		T* operator->() {
			return _ptr;
		}
		//Relationship between RAII and smart pointer
		// RAII is a kind of thinking about managed resources. Intelligent pointer is realized by this kind of RAII, unique_lock,lock_guard
		//RAII uses the life cycle of objects to control program resources
		~SmartPtr() {
			if (_ptr) {//At the end of the object life cycle, the destructor is called to release resources
				delete _ptr;
				_ptr = nullptr;
			}
		}
	private:
		T* _ptr;
	};

We can use the class designed above to test a small program.

#include<iostream>
using namespace std;
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr)//Constructor
		:_ptr(ptr)
	{}

	T& operator*() {
		return *_ptr;
	}

	T* operator->() {
		return _ptr;
	}
	//Relationship between RAII and smart pointer
	// RAII is a kind of thinking about managed resources. Intelligent pointer is realized by this kind of RAII, unique_lock,lock_guard
	//RAII uses the life cycle of objects to control program resources
	~SmartPtr() {
		if (_ptr) {//At the end of the object life cycle, the destructor is called to release resources
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};
int main() {
	SmartPtr<int>sp(new int(1));
	cout << *sp << endl;
	return 0;
	
}

In this way, we don't have to be afraid of memory leakage caused by forgetting to release space, and use the life cycle of objects to manage space.

Summary:

Summarize the principle of smart pointer:
1. RAII characteristics
2. Overloaded operator * and opertaor - >, with pointer like behavior.

4.3 implementation of intelligent pointer

The smart class implemented above seems to have no problem, but when one smart object is assigned to another smart object.

Let's look at:

#include<iostream>
using namespace std;
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr)//Constructor
		:_ptr(ptr)
	{}

	T& operator*() {
		return *_ptr;
	}

	T* operator->() {
		return _ptr;
	}
	//Relationship between RAII and smart pointer
	// RAII is a kind of thinking about managed resources. Intelligent pointer is realized by this kind of RAII, unique_lock,lock_guard
	//RAII uses the life cycle of objects to control program resources
	~SmartPtr() {
		if (_ptr) {//At the end of the object life cycle, the destructor is called to release resources
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};
int main() {
	SmartPtr<int>sp1(new int);
	SmartPtr<int>sp2(new int);
	sp1 = sp2;
	return 0;
}

Operation results:

At this time, the code crashes. This is because we didn't write opeator =. The compiler generates opeator =. The default is value copy, that is, shallow copy, which will make the two pointers point to the same space. At the end of the object life cycle, calling the destructor to destruct the same space twice leads to destruct the same space twice, resulting in program crash.

In order to solve the above problems, c + + proposes the following solutions:

        1. Management transfer: c++98 auto_ptr management transfer
        2. Copy prevention: c++11 unique_ptr copy prevention
        3. Reference count: c++11 shared_ptr / / the problem of circular reference of shared copy needs to break again_ PTR.  

  

 1.auto_ptr

In order to solve this problem, c++98 adopts the transfer of management right. When assignment occurs. Take the following code as an example:

#include<iostream>
using namespace std;
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr)//Constructor
		:_ptr(ptr)
	{}

	T& operator*() {
		return *_ptr;
	}

	T* operator->() {
		return _ptr;
	}
	//Relationship between RAII and smart pointer
	// RAII is a kind of thinking about managed resources. Intelligent pointer is realized by this kind of RAII, unique_lock,lock_guard
	//RAII uses the life cycle of objects to control program resources
	~SmartPtr() {
		if (_ptr) {//At the end of the object life cycle, the destructor is called to release resources
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};
int main() {
	SmartPtr<int>sp1(new int);
	SmartPtr<int>sp2(new int);
	sp2 = sp1;
	return 0;
}

sp2=sp1. At this point, auto_ptr is to release the space managed by SP2 to the space managed by SP1, and_ ptr is set to null. See the code for specific implementation:

	template<class T>
	class auto_ptr {
	public:
		auto_ptr(const T* ptr)//Constructor
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& ap)//copy construction 
			:_ptr(ap._ptr) {
			ap._ptr = nullptr;//Leave your managed blank
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap) {
			if (this != &ap) {
				if (_ptr) {//Free up the original space
					delete _ptr;
				}
				_ptr = ap._ptr;
				ap._ptr = nullptr;
				return *this;
			}
		}
		T& operator*() {

			return *_ptr;
		}
		
		T* operator->() {
			return _ptr;
		}
		
		~auto_ptr() {
			if (_ptr) {
				delete _ptr;
				_ptr = nullptr;
			}
		}
	private:
		T* _ptr;
	};

But this way of writing is very bad. I assigned it to you. Finally, the space under my own management has become empty, which feels like theft. It is also prohibited to use in the company.  

2.unique_ptr

To solve the above problem, c + + introduces unique_ptr (scope_ptr, privatization). Its approach is simple and rough. Since you will have this problem, I won't allow you to copy and assign values.

Corresponding code:

	template<class T>
	class unique_ptr {
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		unique_ptr(const unique_ptr<T>& ap) = delete;
		//Disable all copying and assignment, and assignment is not allowed
		unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;
		T& operator*() {

			return *_ptr;
		}
		
		T* operator->() {
			return _ptr;
		}
		
		~unique_ptr() {
			if (_ptr) {
				delete _ptr;
				_ptr = nullptr;
			}
		}
	private:
		T* _ptr;
	};

3.shared_ptr

c + + to solve unique_ptr can not support copy and assignment, and the scheme of reference counting is introduced in c++11.

When multiple smart pointers are required to point to the same resource, use the smart pointer with reference count.

Every time a smart pointer is added to point to the same resource, the resource reference count is increased by one, and vice versa. When the reference count is zero, the resource is released by the last smart pointer to the resource.

The following figure shows the working process of smart pointer with reference count. sp1 objects and sp2 objects point to the same resource through pointers, and the reference counter records the number of objects referencing the resource.

When the sp1 object is destructed, the value of the reference counter decreases by 1. Since the reference count is not equal to 0, the resource is not released, as shown in the following figure:

sp1 deconstruction

When sp2 objects are also destructed, the reference count is reduced to 0 and the resources are released, as shown in the following figure:

sp2 deconstruction

That is, reference counting can ensure that when multiple smart pointers point to a resource, the resource will be dereferenced and released by all smart pointers, so as to avoid dangling pointers caused by premature release.

template<class T>
	struct Delete
	{
		void operator()(const T* ptr)
		{
			delete ptr;
		}
	};
	//Shared in c++11_ PTR is solved by reference counting
	template<class T, class D = Delete<T>>
	class shared_ptr
	{
	private:
		void AddRef()
		{
			_pmutex->lock();
			++(*_pcount);
			_pmutex->unlock();
		}

		void ReleaseRef()
		{
			_pmutex->lock();
			bool flag = false;
			if (--(*_pcount) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" << _ptr << endl;
					//delete _ptr;
					_del(_ptr);
				}

				delete _pcount;
				flag = true;
			}
			_pmutex->unlock();

			if (flag == true)
			{
				delete _pmutex;
			}
		}
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmutex(new mutex)
		{}

		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new int(1))
			, _pmutex(new mutex)
			, _del(del)
		{}

		~shared_ptr()
		{
			ReleaseRef();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmutex(sp._pmutex)
		{
			AddRef();
		}

		// sp1 = sp1
		// sp1 = sp2
		// sp3 = sp1;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				ReleaseRef();

				_pcount = sp._pcount;
				_ptr = sp._ptr;
				_pmutex = sp._pmutex;

				AddRef();
			}

			return *this;
		}


		// It can be used like a pointer
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmutex;
		D _del;
	};

In the above code implementation, we introduced the delegator, that is, the imitation function, to control how to free space, and the multithreading problem, which is solved by locking.

 4.weak_ptr

weak_ptr is used to solve shared_ The problem of PTR circular reference in this case:

struct ListNode
{
int _data;

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

//Circular reference analysis:
 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_ When next destructs, 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, while node1 is composed of_ prev management_ prev belongs to node2
Member, so this is called circular reference, and no one will release it.

At this point, weak is introduced_ ptr,weak_ptr is also very simple, weak_ptr is generally called weak intelligent pointer. Its reference to resources will not change the reference count of resources. It is usually used as an observer to judge whether resources exist and make corresponding operations according to different situations. See implementation for details:

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		// It can be used like a pointer
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

5. Summary

  1. shared_ptr # increases the reference count of resources, which is often used to manage the life cycle of objects.

  2. weak_ptr # does not increase the reference count of resources. It is often used as an observer to judge whether the object is alive or not.

  3. Use shared_ The normal copy constructor of PTR # will generate additional reference count objects, which may cause the object to be destructed multiple times. Use shared_ The copy constructor of PTR # only affects the increase or decrease of the same reference count of the same resource.

  4. auto_ptr # will copy the previous auto_ptr resource setting nullptr operation;

  5. scoped_ptr# by privatizing the copy structure and assignment function, shallow copy is eliminated;

  6. unique_ptr # eliminates shallow copy by deleting copy construction and assignment function, but introduces copy construction and assignment function with right value reference.

 

 

 

Topics: C++ Back-end