C + + smart pointer

Memory leak

What is a memory leak:
A memory leak is a condition in which a program fails to free memory that is no longer in use 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.

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

Classification of memory leaks

  • Heap Leak: heap memory refers to a piece of memory allocated from the heap through malloc / calloc / realloc /new according to needs during program execution. After it is used up, it must be deleted by calling the corresponding free or delete. Assuming that this part of memory is not released due to the design error of the program, this part of space will no longer be used in the future, and Heap Leak will be generated.
  • System resource leakage refers to that the program uses the resources allocated by the system, such as sockets, file descriptors, pipes, etc. without using the corresponding functions, resulting in the waste of system resources, which can seriously reduce the system efficiency and unstable system execution.

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: ideal state. However, if there is an exception, even if the attention is released, there may still be a problem. Intelligent pointer is needed for management.
  2. Use RAII idea or smart pointer to manage resources.
  3. Some company internal specifications use internally implemented private memory management libraries. This set of library comes with memory leak detection function options.
  4. Something's wrong. Use the memory leak tool to detect it. ps: but many tools are unreliable or expensive.

    To sum up:
    Memory leaks are very common. There are two solutions:
    1. Preventive in advance. Such as smart pointer. 2. Error checking afterwards. Such as leak detection tools

Use and principle of intelligent pointer

RAII

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.).
Obtain resources during object construction, then control the access to resources to keep them effective throughout the object's life cycle, and finally release resources during object deconstruction. In this way, we actually entrust the responsibility of managing a resource to an object. (smart pointer / lock guard: lock_guard,unique_lock)
This approach has two major benefits:
There is no need to explicitly free resources. In this way, the resources required by the object remain valid throughout its lifetime.

#pragma once

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete# " << _ptr << endl;
		delete _ptr;
	}
private:
	T* _ptr;

};
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include "SmartPtr.h"
#
using namespace std;
int div()throw(invalid_argument)
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	{
		throw invalid_argument("Divide by 0 error");
		return a / b;
	}

}
void f1()
{
	int* p = new int;
	SmartPtr<int> Sp(p);
	cout << div() << endl;
	//delete p;
}
int main()
{
	try
	{
		f1();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}


Principle of smart pointer:
1. RAII characteristics
2. Overloaded operator * and operator - >, with pointer like behavior

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete# " << _ptr << endl;
		delete _ptr;
	}
	//It can be used like a pointer
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;

};

auto_ptr

auto_ Implementation principle of PTR: the idea of management right transfer ensures that only one object manages resources and causes the original object to be suspended after copying

namespace app
{
	template <class T>
	class auto_ptr
	{//C++98 --- transfer of management rights to ensure that only one object is managing resources
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~auto_ptr()
		{
			if (_ptr != nullptr)
			{
				cout << "delete# " << _ptr << endl;
				delete _ptr;
				_ptr == nullptr;
			}

		}
		//Transfer of management power
		//ap2(ap1)
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		//assignment
		//ap1 = ap3 ap1 = ap1
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}
		//It can be used like a pointer
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;

	};
}

int main()
{
	app::auto_ptr<int> ap1(new int);//Copy problem
	app::auto_ptr<int> ap2(ap1);
	//*ap1 = 10; error
	//*ap2 = 20;
	return 0;
}

unique_ptr

unique_ Implementation principle of PTR: simple and rough copy prevention
Scoped in boost_ ptr

namespace un
{
	template <class T>
	class unique_ptr
	{//Simple and crude solution: copy prohibited
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			
				cout << "delete# " << _ptr << endl;
				delete _ptr;
			

		}

		//It can be used like a pointer
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>&) = delete;
		unique_ptr<T>operator=(const unique_ptr<T>&) = delete;
	private:
		T* _ptr;

	};
}

shared_ptr

shared_ Principle of PTR: multiple shared are realized by reference counting_ Resources are shared between PTR objects.
Dhared in boost_ PTR -- defect: circular reference

  • shared_ptr maintains a count for each resource to record that the resource is shared by several objects.
  • 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.
  • If the reference count is 0, it means that you are the last object to use the resource, and you must release the resource;
  • If it is not 0, it means that other objects besides themselves are using the resource, and the resource cannot be released. Otherwise, other objects will become wild pointers
template <class T>
	class shared_ptr
	{//Simple and crude solution: copy prohibited
	private:
		void Addref()
		{
			_mtx->lock();
			++(*_count);
			_mtx->unlock();
		}
		void Releaseref()
		{
			_mtx->lock();
			bool flag = false;
			if (--(*_count) == 0)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _count;
				flag = true;
			}
			_mtx->unlock();
			if (flag == true)
			{
				delete _mtx;
			}
		}
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _count(new int(1))
			, _mtx(new mutex)
		{}
		~shared_ptr()
		{
			Releaseref();
		}
		
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _count(sp._count)
			, _mtx(sp._mtx)
		{
			Addref();
		}
		//sp1 = sp2 sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Releaseref();
				_count = sp._count;
				_ptr = sp._ptr;
				_mtx = sp._mtx;
				Addref();
			}
			return *this;
		}

		//It can be used like a pointer
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		int use_count()
		{
			return *_count;
		}
	
	private:
		T* _ptr;
		int* _count;
		mutex* _mtx;//Solve thread safety problems
	};

weak_ptr

It's the weak in boost_ PTR is mainly used to solve shared_ Circular reference of PTR

template<class T>
	class weak_ptr
	{
		//Do not participate in resource management and do not add shared_ptr manages the reference count of resources, which can be referenced like a pointer
	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;
	};

Delete device

Solve the problem that objects that are not new are managed through smart pointers

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;
		}


		// You can use it 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;
	};

	//Custom delegator - the delegator controls how resources are released
	template<class T>
	struct DelArr
	{
		void operator()(const T* ptr)
		{
			cout << "delete[]: " << ptr << endl;
			delete[] ptr;
		}		
	};

Keywords: C++

Added by mustatin on Thu, 24 Feb 2022 04:49:33 +0200