[C ++ in-depth analysis] 25. Smart pointers in C ++

Problems often occur in dynamic memory management: one is to forget to release the memory, which will cause a memory leak; referring to the released memory will generate a pointer that refers to illegal memory. Introduce smart pointer to solve the above problem, and automatically release the pointed object. shared_ptr allows multiple pointers to point to the same object, and unique_ptr "exclusively" points to the object. weak_ptr is a weak reference to the object managed by shared_ptr. These three smart pointers are defined in the memory header file.

1 shared_ptr

1.1 usage of shared_ptr

  • Use the make_shared function, which allocates an object in dynamic memory and initializes it, and returns the shared_ptr pointing to this object.
  • Use ordinary pointers to call member function creation.
shared_ptr<int> p3 = make_shared<int>(42);

shared_ptr<int>pp(new int(10));

Each shared_ptr has a reference count, we copy a shared_ptr, and the count is increased by 1. When we assign a new value to shared_ptr or the shared_ptr is destroyed, the count is decremented by 1. When the counter becomes 0, the managed object is automatically released.

  • Operations supported by both shared_ptr and unique_ptr

Insert picture description here

  • Shared_ptr unique operation

Insert picture description here

  • Other ways to define and change shared_ptr

Insert picture description here
Notes for use :
1. Do not mix common pointers and smart pointers.
If mixed, after the smart pointer is automatically released, the common pointer sometimes becomes a dangling pointer. When binding a shared_ptr to a normal pointer, you should use shared_ptr. Operation, you should no longer use the built-in pointer to access memory.

2. You cannot directly assign pointers to shared_ptr, one of them is a pointer and the other is a class object

3. Do not use get to initialize another smart pointer or assign a value to a smart pointer

shared_ptr<int> p(new int(42));	//引用计数为1
int *q = p.get();				//正确:但使用q时要注意,不要让它管理的指针被释放
{
    shared_ptr(q);				//未定义:两个独立的share_ptr指向相同的内存
}								//程序块结束,q 被销毁,它指向的内存被释放
int foo = *p;					//未定义,p指向的内存已经被释放了

p and q point to the same block of memory. Since they are created independently of each other, their respective reference counts are 1. When the block where q is located ends, q is destroyed, causing the memory to be released. At this time, p becomes a dangling. Pointer, use will cause undefined behavior, when p is destroyed, this space will be deleted twice

1.2 Implementation principle

Let's take a look at how it is achieved:

template<class T>
class SharedPtr					//模拟实现shared_ptr
{
public:
	SharedPtr(T* tmp = nullptr) : _ptr(tmp), count(new int(1)) { }

	~SharedPtr()
	{
		if (--(*count) == 0)
		{
			delete _ptr;
			delete count;
		}
	}

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

	T* operator->()
	{
		return _ptr;
	}
	
	SharedPtr(SharedPtr<T>& tmp) : _ptr(tmp._ptr), count(tmp.count)
	{
		(*count)++;
	}
	
	SharedPtr<T>& operator = (SharedPtr<T>& tmp)
	{
		if (_ptr != tmp._ptr)		//排除自己给自己赋值的可能
		{
			if (--(*count) == 0)	//先要判断原来的空间是否需要释放
			{
				delete _ptr;
				delete count;
			}
			_ptr = tmp._ptr;
			count = tmp.count;
			(*count)++;
		}
		return *this;				//考虑连等的可能
	}
private:
	T* _ptr;
	int* count;
};

Here we must first talk about why the record reference count is a pointer: in order to make each object use the same variable tag, so you can not directly use integer records. Some people say that the line, use a static bar, it does not work, the use of static is really a common variable for all variables. For example, pointer a, b points to memory block A, pointer c, d, e points to memory block B, Originally, the reference counts of a and b should be 2, and the reference counts of c, d, and e should be 3. If you want to use statics, it will become 5. Pay attention here, so the best way is to use pointers.

1.3 Circular reference problems caused by shared_ptr

Circular references can cause memory leaks. First, let's understand what circular references are. Here we use a linked list example to implement it:

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
 
	ListNode(int x)
		:_data(x)
		, _prev(NULL)
		,_next(NULL)
	{}
	~ListNode()
	{
		cout << "~ListNode" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> cur(new ListNode(1));
	shared_ptr<ListNode> next(new ListNode(2));
	cur->_next = next;
	next->_prev = cur;
	cout << "cur" << "     " << cur.use_count() << endl;
	cout << "next" << "     " << next.use_count() << endl;
	return 0;
}

Insert picture description here
In order to solve this problem, the C ++ library defines weak_ptr, which is specially used to assist shared_ptr to solve the reference counting problem. When shared_ptr is to monitor other shared_ptr objects internally, weak_ptr is used, which does not increase the monitored reference count, and automatically expires when the monitored object is destructed.

2 unique_ptr

2.1 Use of unique_ptr

Only one unique_ptr can point to a given object at a time, and unique_ptr does not support ordinary copy or assignment operations.

  • unique_ptr operation

Insert picture description here
Although we cannot copy or assign unique_ptr, we can transfer pointer ownership from one (non-const) unique_ptr to another unique_ptr by calling release or reset

//将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p2(p1.release());

//将所有权从p3转移到p2,
unique_ptr<string>p3(new string("Trex"));
p2.reset(p3.release());

2.2 Implementation principle

The principle of unique_ptr: directly define the copy construction / assignment function as private, and only declare that it is not implemented. Class members cannot call these two functions.

template<class T>
class UniquePtr					//模拟实现unique_ptr
{
public:
	UniquePtr(T* tmp = nullptr)	: _ptr(tmp)	{ }
	~UniquePtr()
	{
		if (_ptr)
			delete _ptr;
	}

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

	T* operator->()
	{
		return _ptr;
	}
private:
	UniquePtr(const UniquePtr<T>& tmp) = delete;
	UniquePtr<T>& operator=(const UniquePtr<T>& tmp) = delete;
	T* _ptr;
};

3 weak_ptr

weak_ptr It points to an object managed by shared_ptr, weak_ptr does not change the reference count. Once the last shared_ptr pointing to the object is destroyed, the object will be released, even if there is weak_ptr pointing to the object, the object will still be released.

  • Operation of weak_ptr

Insert picture description here

4 Are smart pointers thread safe

  • For unique_ptr, it is only valid within the current code block. So it does not involve the issue of thread safety.
  • For shared_ptr, multiple objects need to share a reference count variable at the same time, so there will be a problem of thread safety, but the standard library takes this into account when implementing it, and uses an atomic operation (CAS) -based approach to ensure that shared_ptr is efficient and atomic Operation reference count.

5 Summary

1. In the case of copy construction / assignment, it is recommended to use shared_ptr
2. When copy construction / assignment is not required, you can use unique_ptr
3. When there are other shared_ptr objects in the class, use weak_ptr without changing the reference count.

Published 298 original articles · praised 181 · 100,000+ views

Guess you like

Origin blog.csdn.net/happyjacob/article/details/104582363