Why do you need smart pointers?

Why do you need smart pointers?

  1. Solve the problem of memory leak caused by forgetting to release memory.
  2. Resolve exception security issues.
#include<iostream>
using namespace std;

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

Question: p1What will happen if new throws an exception here?

Answer: p1、p2The space cannot be opened and the memory is not released.

Question: p2What will happen if new throws an exception here?

Answer: p2The space cannot be opened and the memory has not been released.

Question: What will happen if the div calls this and throws an exception?

Answer: The memory is not released.

So how to solve it?

You can use smart pointers to solve this problem.

The use and principle of smart pointers

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:

  • There is no need to explicitly release resources.
  • In this way, the resources required by the object remain valid throughout its lifetime.

We use a class to encapsulate this pointer and implement it as follows:

#include<iostream>
using namespace std;

namespace hayaizo
{
    
    
	template<class T>
	class smart_ptr
	{
    
    
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		~smart_ptr()
		{
    
    
			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()
{
    
    
	hayaizo::smart_ptr<int> sp1(new int);
	hayaizo::smart_ptr<int> sp2(new int);
	cout << div() << endl;
}

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

	return 0;
}

operation result:

Since it is a pointer, it also needs to support dereference *and ->overloading these two symbols.

namespace hayaizo
{
    
    
	template<class T>
	class smart_ptr
	{
    
    
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

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

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

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

auto_ptrBut such encapsulation will have a fatal flaw. An address can only be pointed to by a smart pointer, otherwise it will cause the problem of releasing the same memory twice. Let's see how the official library solves it.

As you can see, sp1the address of becomes sp2the address of , and then sp1the address becomes nullptr.

Rub a low-profile version by hand auto_ptr:

namespace hayaizo
{
    
    
	template<class T>
	class auto_ptr
	{
    
    
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

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

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
    
    
			if (this != &ap)
			{
    
    
				if (_ptr)
				{
    
    
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

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

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

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

But this can indeed solve the problem, but it has lost the function of the native pointer. The native pointer supports the same address being pointed to by many pointers. It must be introduced before introducing the solution unique_ptr.

unique_ptr

unique_ptrIt's very rude. It doesn't allow you to copy directly, and the copy structure is disabled.

	template<class T>
	class unique_ptr
	{
    
    
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		unique_ptr(unique_ptr<T>& ap)=delete
		{
    
    }

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{
    
    }

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

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

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

shared_ptr

The principle of shared_ptr is to realize resource sharing between multiple shared_ptr objects through reference counting.

  1. Internally, 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 is called), it means that the resource is no longer used, and the reference count of the object is decremented 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.

For example, I use three smart pointers to point to the address 0x00112233, so the count is 3, and it will be destroyed when the count equals 0.

So, can the count here be simply represented by an int _cntor static int _cnt?

the answer is negative.

If so int _cnt, then each object is counted individually.

If it is ``static int _cnt`, then the same count is used for each object.

So a pointer is needed to represent the count.

template<class T>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

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

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

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

}

circular reference

n1The reference counts of the sum here n2are both 2, so a mutual restriction situation is formed.

n1Look at the destruction n2, n2look at the destruction n1.

weak_ptr

weak_ptr is a smart pointer introduced to cooperate with shared_ptr.

A weak_ptr can be constructed from a shared_ptr or another weak_ptr object. Its construction and destruction will not cause the shared_ptr reference count to increase or decrease.

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

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

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

	private:
		T* _ptr;
	};

struct Node
{
    
    
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

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


int main(void)
{
    
    
	hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;


	return 0;
}

It's very simple, that is, n1the internal pointer does not participate in the reference count. Just encapsulate it with another class, and shared_ptr<T>the reference count inside will not be touched.

Custom deleter

In fact, it is a functor, and you can pass the deletion plan yourself.

//默认删除器
	template<class T>
	struct Delete
	{
    
    
		void operator()(T* ptr)
		{
    
    
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

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

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

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};
template<class T>
struct DeleteArray
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 

Total code:

//#include<iostream>
//using namespace std;
//
//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;
//}

#include<iostream>
using namespace std;

namespace hayaizo
{
    
    
	template<class T>
	class auto_ptr
	{
    
    
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

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

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
    
    
			if (this != &ap)
			{
    
    
				if (_ptr)
				{
    
    
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

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

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

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

	template<class T>
	class unique_ptr
	{
    
    
	public:
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    }

		unique_ptr(unique_ptr<T>& ap)=delete
		{
    
    }

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{
    
    }

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

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

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

	//默认删除器
	template<class T>
	struct Delete
	{
    
    
		void operator()(T* ptr)
		{
    
    
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{
    
    }

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{
    
    }

		void Release()
		{
    
    
			if (--(*_cnt) == 0)
			{
    
    
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
    
    
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
    
    
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
    
    
			return *_cnt;
		}

		T* get() const
		{
    
    
			return _ptr;
		}

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

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

		~shared_ptr()
		{
    
    
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

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

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

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

	private:
		T* _ptr;
	};

}

struct Node
{
    
    
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

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

template<class T>
struct DeleteArray
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
    
    
	void operator()(T* ptr)
	{
    
    
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 


int main(void)
{
    
    
	/*hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;*/
	hayaizo::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
	hayaizo::shared_ptr<Node> n2(new Node);
	hayaizo::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
	hayaizo::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int)));


	return 0;
}

Guess you like

Origin blog.csdn.net/AkieMo/article/details/132252552