[C++]——c++11 smart pointers

Foreword:

  • In this issue, what we are going to learn is the new concept proposed in C++11-exception pointers!

Table of contents

(1) Introduction of smart pointers

(2) Memory leaks

1. What is a memory leak and the dangers of a memory leak?

2. Memory leak classification (understanding)

3. How to detect memory leaks (understand)

4. How to avoid memory leaks

 (3) The use and principle of smart pointers

1、RAII

2. Principle of smart pointer  

3、std::auto_ptr

4、std::unique_ptr

5、std::shared_ptr

6、weak_ptr

7. Deleteer

(4) The relationship between smart pointers in C++11 and boost

Summarize


(1) Introduction of smart pointers

The applied space (that is, the space created by new ) needs to be deleted at the end of use , otherwise memory fragmentation will occur. During the running of the program, the new object is deleted in the destructor , but this method cannot solve all problems, because sometimes new occurs in a global function, and this method will cause mental burden on the programmer. At this point, smart pointers come in handy . Using smart pointers can avoid this problem to a large extent, because a smart pointer is a class. When the scope of the class is exceeded, the class will automatically call the destructor, and the destructor will automatically release resources. Therefore, the working principle of smart pointers is to automatically release memory space when the function ends, avoiding manual release of memory space.
Let's first analyze whether there are any memory problems in the following program? A reminder: pay attention to the problems in the MergeSort function:
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

【explain】

  1. p1 p1 The pointer will remain a null pointer because the pointer has not yet been allocated a valid memory address before the exception is thrown  .

  2. Since  the pointer has not been allocated valid memory, it is unsafe p1 to delete in subsequent code  .p1 

  3. p2 The pointer will also not be allocated valid memory, because after  p1 the exception is thrown, the program flow will jump directly to the exception handling block without continuing  p2 the allocation.

  4. div() Exceptions in the function will be thrown and  main() caught in the function's exception handling block.

In short, if  new int  an exception is thrown during the memory allocation process, the pointer  p1  and  p2  will remain uninitialized, and the allocated memory will not be released, resulting in a memory leak.

(2) Memory leaks

1. What is a memory leak and the dangers of a memory leak?

What is a memory leak : A memory leak refers to a situation where a program fails to release memory that is no longer in use due to negligence or error. Memory leaks do not mean the physical disappearance of memory, but that after the application allocates a certain segment of memory, it loses control of the memory segment due to design errors, thus causing a waste of memory.
The harm of memory leaks : Memory leaks in long-running programs, such as operating systems, background services, etc., have a great impact. Memory leaks will cause the response to become slower and slower, and eventually freeze.
void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  
  delete[] p3;
}

【explain】

In the above code, several memory leaks and exception security issues are involved. Let me briefly analyze it:

Memory is allocated but forgotten to be released:

  • In the following lines,  malloc memory is allocated, but no corresponding  free memory is released:
int* p1 = (int*)malloc(sizeof(int));
  • Likewise, in the following lines, use is made  new to allocate memory, but there is no corresponding use  delete to free the memory:
int* p2 = new int;
  • These memory allocations are not freed, causing memory leaks, and the allocated memory is not released even after the program ends.

Exceptional security issues:

  • In the following lines, use is used  new to allocate an array of integers, but there is no corresponding way  delete[] to free the memory:
int* p3 = new int[10];
  • When  Func() an exception occurs inside a function, delete[] p3 it will not be executed, resulting in a memory leak when the function returns.

Memory leak caused by exception:

  • In  Func() the function, if an exception occurs, delete p1 and  delete p2 will not be executed, causing   the memory allocated by p1 and  not to be released.p2

In order to solve these problems, everyone needs to:

  • free Use or  to free memory when appropriate  delete to avoid memory leaks.
  • After allocating memory, make sure to use  try blocks so that the allocated memory can be freed when an exception occurs.
  • Be careful when handling exceptions to ensure that you do not re-free memory that has already been freed.

In summary, writing robust code requires attention to resource management and exception handling to ensure that memory leaks and other exception issues are minimized.


2. Memory leak classification (understanding)

In C/C++ programs, we generally care about two aspects of memory leaks:
Heap leak
  • Heap memory refers to a piece through malloc / calloc / realloc / new , etc. according to needs during program execution. After use, it must be deleted by calling the corresponding free or delete . If a design error in the program results in this part of the memory not being released, then this part of the space will no longer be able to be used, and a Heap Leak will occur .
System resource leak
  • It means that the program uses resources allocated by the system, such as sockets, file descriptors, pipes, etc., without using the corresponding functions to release them, resulting in a waste of system resources, which can seriously lead to reduced system performance and unstable system execution.

3. How to detect memory leaks (understand)

Memory leak detection under Linux: Tool for detecting memory leaks under Linux
Using third-party tools under Windows: VLD tool instructions

4. How to avoid memory leaks

1. Establish good design standards in the early stage of the project, develop good coding standards, and remember to release the allocated memory space in a matching manner. ps : This is the ideal state. But if you encounter an exception, even if you pay attention to release, there may still be problems. It needs to be managed by the next smart pointer to be guaranteed.
2. Use RAII ideas or smart pointers to manage resources.
3. Some companies' internal specifications use internally implemented private memory management libraries. This library comes with built-in memory leak detection options.
4. If something goes wrong, use the memory leak tool to detect it. ps : However, many tools are unreliable or expensive.
【summary】
Memory leaks are very common and there are two solutions:
  • 1. Preventive type . Such as smart pointers , etc.
  • 2. Check for errors afterwards . Such as leak detection tools .

 (3) The use and principle of smart pointers

1、RAII

RAII ( Resource Acquisition Is Initialization ) is a simple technology that uses the object life cycle to control program resources (such as memory, file handles, network connections, mutexes, etc.).

Obtain resources when the object is constructed , then control access to the resources so that they remain valid during the object's life cycle, and finally release the resources when the object is destroyed . With this, we actually delegate the responsibility of managing a resource to an object. This approach has two major benefits:
  • No need to explicitly release resources.
  • In this way, the resources required by the object remain valid throughout its lifetime

At this time, for the code we gave above,  a solution to the SmartPtr class designed using RAII ideas is given:

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			cout << "delete:" << _ptr << endl;
			delete _ptr;
	}

private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

Output display:

【explain】

To appeal to this approach, the resources applied for at this time are not managed by oneself but handed over to the smart pointer, that is, the smart pointer is used to construct a smart pointer object;

Regarding the three situations of p1, p2 and div system calls mentioned earlier, we will analyze it at this time and see what it looks like in such a scenario?

  • According to the output display effect, we can find that they are all normal releases.
  • At this time, some young people are curious, why did we release it without deleting it? In fact, this is because sp1 and sp2 are both local objects. When a local object goes out of scope, its destructor will be called.

If sp1 throws an exception:

  • If this new throws an exception, it will not enter the constructor, because this new is an actual parameter. The actual parameter will call new first, and new will call operator new and throw an exception. Will it leave directly, so there is no need to release resources? What;

If sp2 throws an exception:

  • If the second one throws an exception, speaking of throwing an exception here, it jumps directly to the catch area. In fact, it will end the stack frame first, and the object inside will call the destructor. Calling the destructor will release the resource managed by SP2;

If div() throws an exception:

  • If div  throws an exception, it will end this function, and these local objects will call the fictitious function, and the release will be completed.


2. Principle of smart pointer 

The above-mentioned SmartPtr cannot be called a smart pointer because it does not yet have the behavior of a pointer. The pointer can be dereferenced, or the content in the pointed space can be accessed through ->. Therefore: * and -> must be overloaded in the  AutoPtr template class before it can be used like a pointer .

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

T* operator->()
{
	return _ptr;
}
  • At this point, we can use it normally like a pointer:

To summarize the principles of smart pointers:
  • 1. RAII characteristics
  • 2. Overload operator* and opertaor-> , which behave like a pointer.

3、std::auto_ptr

auto_ptr documentation introduction

auto_ptr  is a smart pointer introduced in the C++98 standard for managing dynamically allocated memory resources. It provides a simple ownership transfer mechanism that allows resource ownership to be transferred from one  auto_ptr  instance to another.

Here are some  important things to know about auto_ptr  :

  • Ownership transfer :  auto_ptr  allows resource ownership to be transferred from one instance to another through an assignment operation. This means that once a resource is assigned to another  auto_ptr  , the original  auto_ptr  no longer owns the resource:

int main() 
{
    auto_ptr<int> ptr1(new int(5));
    auto_ptr<int> ptr2;

    ptr2 = ptr1; // 所有权转移

    //cout << *ptr2 << endl; // 输出 5
    cout << *ptr1 << endl; // 错误!ptr1 不再拥有指针,已经转移给了 ptr2

    return 0;
}

Output display:

【explain】

  1. In the above code, ptr1 we have a dynamically allocated inttype pointer and assign it toptr2;
  2. Because of the ownership transfer feature of auto_ptr ptr1 , the pointer is no longer owned at this time, but the ownership is transferred to ptr2. Therefore, trying to use ptr1 dereferencing results in undefined behavior.

  • Dangling pointer problem :  auto_ptr  has a dangling pointer problem, that is, after the resource ownership is transferred, the original  auto_ptr  will become a null pointer, but the resource may still be used by another  auto_ptr  , which may lead to unpredictable behavior.

int main() 
{
    auto_ptr<int> ptr(new int(5));

    if (true) 
    {
      auto_ptr<int> otherPtr = ptr;
      //...
    }

   cout << *ptr << endl; // 输出不确定的值,可能导致程序崩溃

    return 0;
}

Output display:

【explain】

  1. In the above code, ptr we have a dynamically allocated int type pointer. This pointer is then transferred to otherPtr, and ifafter the statement block ends, otherPtrit goes out of scope, frees the pointer, and sets it tonullptr;
  2. At this point, ptr it becomes a dangling pointer, and accessing it results in undefined behavior.

  【summary】

C++ references the smart pointer auto_ptr to help automate this process . Subsequent programming experience (especially using the STL ) showed that a more refined mechanism was needed. Based on programmers' programming experience and the solutions provided by the BOOST library, C++11 abandoned auto_ptr and added three new smart pointers: unique_ptr, shared_ptr and weak_ptr . All new smart pointers work with STL containers and move semantics.

4、std::unique_ptr

unique_ptr documentation

Because of  these problems with auto_ptr  , it was deprecated in the C++11 standard. In modern C++, it is recommended to use  unique_ptr instead of  auto_ptr  . unique_ptr provides better semantics and security, while supporting move semantics and custom deleters, making resource management more flexible and reliable.

The implementation principle of unique_ptr : simple and crude copy prevention. The following simplified simulation implements a copy of UniquePtr to understand its principle :
	template<class T>
	class unique_ptr
	{
	public:

		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		unique_ptr(unique_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

		//c++11 思路:语法直接支持
		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

		//c++98思路:只声明不实现,但是用的人可能会在外面强行定义,所以可以声明为私有
	/*private:
		unique_ptr(const unique_ptr<T>& up);*/

	private:
		T* _ptr;
	};


	void Test_unique()
	{
		unique_ptr<int> up1(new int);
		unique_ptr<int> up2(up1);
	}

Output display:

When we use the one that comes with the compiler, let’s see what the effect looks like:

【explain】

  1. Trying to copy-construct std::unique_ptr will result in a compilation error;
  2. The problem is that  unique_ptr  is a smart pointer with exclusive ownership. This means that a  unique_ptr  can only own one resource and is not allowed to copy it through the regular copy constructor;
  3. move can be used to move resource ownership from one  unique_ptr  to another, but direct copy construction is not allowed.

Code modification:

【explain】

  1. In this revised code, the move function  up1 transfers the resource ownership in  up2to avoid compilation errors.
  2. In summary, the error is  caused by the exclusive ownership nature of unique_ptr   . It only allows a unique owner of a resource, so a unique_ptr  cannot be copied via the regular copy constructor . To transfer resource ownership, use the move function.

5、std::shared_ptr

shared_ptr documentation

shared_ptr was introduced  to solve the problem of shared ownership in resource management . In many cases, multiple pointers need to own the same resource, and it is necessary to ensure that the resource is not destroyed until the last pointer using it is released. shared_ptr provides a smart pointer implementation that allows multiple pointers to share ownership of a resource while ensuring safe release of the resource.

The principle of shared_ptr is to realize resource sharing between multiple shared_ptr objects through reference counting . For example: The teacher will notify the last students to leave before leaving get off work in the evening, so that the last students to leave remember to lock the door.
  • 1. Shared_ptr internally maintains a count for each resource to record that the resource is shared .
  • 2. When the object is destroyed ( that is, the destructor is called ) , it means that you are no longer using 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 must release the resource ;
  • 4. If it is not 0 , it means that other objects besides itself are using the resource, and the resource cannot be released , otherwise other objects will become wild pointers.
template<class T>
	class shared_ptr
	{
	public:

		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
			,_pmtx(new mutex)
		{}

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

		void Release()
		{
			_pmtx->lock();
			bool flag = false;

			if (--(*_pcount) == 0 && _ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;

				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		void AddRef()
		{
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();
		}

		shared_ptr<T>& operator = (const shared_ptr<T> sp)
		{
			if (_ptr != sp._ptr) //防止自己给自己赋值
			{
				Release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmtx = sp._pmtx;

				AddRef();
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}


		~shared_ptr()
		{
			Release();
		}

		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

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

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;
	};

Output display:

【explain】

  1. The above code shows a simplified version of C++11 shared_ptr implementation. This is a template class that manages dynamically allocated memory and implements shared ownership;
  2. The implementation method is to use reference counting technology, which records the number of shared pointers pointing to the same dynamically allocated memory and releases the memory when the last pointer is destroyed;
  3. In order to ensure thread safety, a mutex lock is used to synchronize the increase and decrease operations of the counter;
  4. In addition, in order to make shared pointers can be used like ordinary pointers, overloaded dereference operators and member access operators are also provided.

Thread safety issues of shared_ptr:
Through the following program we test the thread safety issue of shared_ptr . It should be noted that the thread safety of shared_ptr is divided into two aspects:
  • 1. The reference count in a smart pointer object is shared by multiple smart pointer objects. The reference count of the smart pointer in two threads is ++ or -- at the same time. This operation is not atomic. The reference count was originally 1 and ++ ed twice. , maybe still 2. In this way, the reference count will be messed up. This can cause problems such as resources not being released or the program crashing. Therefore, reference counting ++ and -- in smart pointers need to be locked, which means that reference counting operations are thread-safe.
  • 2. The object managed by the smart pointer is stored on the heap, and accessing it in two threads at the same time will lead to thread safety issues.
Code display:
	struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;

		~Date()
		{}
	};

	void SharePtrFunc(zp::shared_ptr<Date>& sp, size_t n, mutex& mtx)
	{
		cout << sp.get() << endl;
		for (size_t i = 0; i < n; ++i)
		{
			// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
			zp::shared_ptr<Date> copy(sp);
			// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n
			//次,但是最终看到的结果,并一定是加了2n
			{
				unique_lock<mutex> lk(mtx);
				copy->_year++;
				copy->_month++;
				copy->_day++;
			}
		}
	}

	void test_shared_safe()
	{
		zp::shared_ptr<Date> p(new Date);
		
		cout << p.get() << endl;
		
		const size_t n = 50000;
		mutex mtx;
		thread t1(SharePtrFunc, ref(p), n, ref(mtx));
		thread t2(SharePtrFunc, ref(p), n, ref(mtx));

		t1.join();
		t2.join();

		cout << p.use_count() << endl;

		cout << p->_year << endl;
		cout << p->_month << endl;
		cout << p->_day << endl;
	}

Output display:

  • When we are not performing locking operations:

  • When we perform a locking operation:


Circular reference of shared_ptr:

  • Next, we first give the code for analysis and explanation:
	struct ListNode
	{
		int _data;
		zp::shared_ptr<ListNode> _prev;
		zp::shared_ptr<ListNode> _next;

		~ListNode()
		{ 
			cout << "~ListNode()" << endl; 
		}
	};

	void Test_cycle()
	{
		zp::shared_ptr<ListNode> node1(new ListNode);
		zp::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;
	}

Output display:

【explain】

  • 1. The two smart pointer objects node1 and node2 point to two nodes, the reference count becomes 1 , and we do not need to delete manually .
  • 2. node1 's _next points to node2 , 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 still points to the next node. But _prev also points to the previous node.
  • 4. In other words , _next is destructed and node2 is released.
  • 5. In other words , _prev is destructed and node1 is released.
  • 6. However, _next is a member of node . When node1 is released, _next will be destructed. Node1 is managed by _prev , and _prev is a member of node2 , so this is called a circular reference and no one will release it.

Due to the problem of circular references, when the program exits Test_cycle(), the reference counts of the two nodes will not drop to 0, so their destructors will not be called. This means that the output statements in the destructor will not be executed, potentially leading to the risk of memory leaks.

In order to avoid circular reference problems, you can set the _prev and _next member variables as weak_ptr, which is a weak reference to shared_ptr and does not increase the reference count. In this way, in the circular linked list, weak_ptr is used to break the strong reference relationship and prevent memory leaks caused by circular references.

  • Here is sample code to fix the circular reference problem:

First, we first implement a weak_ptr manually  or use the weak_ptr provided in the library. Here, I implemented one manually:

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

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

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

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

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};

The corrected code is as follows:

	struct ListNode
	{
		int _data;
		zp::weak_ptr<ListNode> _prev;
		zp::weak_ptr<ListNode> _next;


		~ListNode()
		{ 
			cout << "~ListNode()" << endl; 
		}
	};

	void Test_cycle()
	{
		zp::shared_ptr<ListNode> node1(new ListNode);
		zp::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;
	}

Output display:

【explain】

  1. In the repaired code, change the member variables _prev and _next in the ListNode structure to the zp::weak_ptr type, so that a strong reference to the next node will not be added and the circular reference problem will be avoided;
  2. When the program exits the Test_cycle() function, the reference counts of node1 and node2  will drop to 0, the destructor  ~ListNode() will be called, and the related memory resources will be released correctly, avoiding the problem of memory leaks.

6、weak_ptr

In the above shared_ptr description, we used  weak_ptr. Next, we will formally introduce it.

basic introduction:

  • weak_ptr is a smart pointer that does not control the life cycle of the object . It points to an object managed by shared_ptr ;
  • The object's memory management is performed by the strongly referenced shared_ptr . weak_ptr only provides a means of access to managed objects;
  • The purpose of weak_ptr design is to introduce a smart pointer to cooperate with shared_ptr to assist shared_ptr work. It can only be constructed from a shared_ptr or another weak_ptr object . Its construction and destruction will not cause the reference count to increase or decrease ;
  • weak_ptr is used to solve the deadlock problem when shared_ptr refers to each other. If two shared_ptr refers to , then the reference count of the two pointers can never drop to 0, and the resource will never be released;
  • It is a weak reference to the object and does not increase the reference count of the object. It can be converted to and from shared_ptr . shared_ptr can be directly assigned to it. It can obtain shared_ptr by calling the lock function.

Next, let's take a brief look at the code:

class B;
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());

	pb->pa_ = pa;
	pa->pb_ = pb;

	cout << pb.use_count() << endl;
	cout << pa.use_count() << endl;
}
int main()
{
	fun();
	return 0;
}

Output display:

【explain】

  1. You can see that in the fun function , pa and pb refer to each other. The reference counts of the two resources are 2. When the function is to be jumped out, the reference counts of the two resources will be reduced by one when the smart pointers pa and pb are destructed;
  2. However, the reference count of both is still 1 , resulting in resources not being released when jumping out of the function ( the destructor of AB is not called). If one of them is changed to weak_ptr, it will be fine. We change the shared_ptr pb_ in class A ;
Change to weak_ptr pb, the running results are as follows:

  • In this case, the reference of resource B is only 1 at the beginning . When pb is destructed, B 's count becomes 0 , and B is released. When B is released, A 's count will also be reduced by one. At the same time, when pa is destructed, A 's count will be reduced. The count is decremented by one, then A 's count is 0 and A is released.

Note : We cannot directly access the object's method through weak_ptr , we should convert it to shared_ptr first! ! !


7. Deleteer

If the object is not new, how can it be managed through smart pointers? In fact, shared_ptr designed a deleter to solve this problem! !

1. Definition

  1. Smart pointer deleter refers to a custom operation performed when a smart pointer is destructed;
  2. The deleter can perform additional cleanup work or custom logic when releasing the resources managed by the smart pointer.

  • Here's an example of using a Lambda expression as a deleter:
int main()
{
    int* p = new int(10);
    std::shared_ptr<int> sp(
        p, 
        [](int* ptr) 
        { 
            std::cout << "deleting pointer " << ptr << std::endl; delete ptr; 
        });

    // 输出共享指针的引用计数
    std::cout << "sp use_count: " << sp.use_count() << std::endl;

    // 手动将共享指针的引用计数减 1
    sp.reset();

    return 0;
}

Output display:

【explain】

  1. The above code creates a pshared pointer pointing to an integer variable and uses a deleter Lambda expression to output the address of the deleted object and release the memory allocated on the heap;
  2. After sp.reset()the call, the reference count becomes zero and the deleter is called to free the memory.

(4) The relationship between smart pointers in C++11 and boost

First, C++11 introduced smart pointers in the standard library, including std::shared_ptrand std::unique_ptr. These smart pointers provide a mechanism for managing resource ownership, which can automatically perform memory management and avoid the trouble of manually releasing resources. C++11's smart pointers are implemented by introducing new language features and library support.

Boost is a popular C++ extension library that provides a large collection of high-quality, widely tested and used C++ code. Before the C++11 standard introduced smart pointers, Boost already provided its own smart pointer library, including boost::shared_ptrand boost::scoped_ptretc. These smart pointers are widely used and highly praised in the C++ community.

In fact, the smart pointers in the C++11 standard library are influenced and inspired by Boost smart pointers. std::shared_ptrThe design and functionality of sum in the C++11 standard are basically quite similar to sum std::unique_ptrin Boost . C++11 smart pointers also introduce some new features and improvements, such as move semantics and custom deleters, to provide better performance and flexibility.boost::shared_ptrboost::scoped_ptr


Summarize

At this point, the explanation about smart pointers is all over. Next, briefly review and summarize this article! ! !

A smart pointer is a pointer used to automatically manage dynamically allocated resources. It provides automated memory management that can reduce common resource management problems such as memory leaks and dangling pointers.

Common smart pointer types:

  • std::shared_ptr: Allows multiple pointers to share the same memory resource and uses reference counting for memory management.
  • std::unique_ptr: Exclusive pointer, which guarantees that only one pointer can access the resource, has move semantics, and can be used to realize the transfer of ownership.
  • std::weak_ptr: Weak reference pointer, used to solve std::shared_ptrthe problem of resource leaks caused by circular references.

Advantages of smart pointers:

  • Automatically release resources : Smart pointers automatically release managed resources through destructors, avoiding the cumbersome process of manually releasing resources.
  • Avoid memory leaks : Smart pointers use reference counting or exclusive ownership to ensure that resources are released correctly when they are no longer used, thus avoiding memory leaks.
  • Improve security: Smart pointers can reduce the problems of dangling pointers and wild pointers, and improve the security and stability of the program.

The above is the entire content of this article. Thank you everyone for watching and supporting! ! !

 

Guess you like

Origin blog.csdn.net/m0_56069910/article/details/132595213