C++---The principle of smart pointer explained

Why do you need smart pointers

When writing code, the space applied for from the heap was not released due to some reasons (abnormal in the middle of the code), which led to a memory leak in the code. Therefore, in order to prevent such problems, smart pointers appeared. Using smart pointers can automatically detect, if the pointer is not used, it will automatically release the space on the heap.

The role of smart pointers
Manage pointers and automatically release resources.
Insert picture description here

RAII

RAII is a technology that uses object life cycle to control program resources

In the class, the resources are acquired when the object is constructed, and finally the resources are released after the object is destroyed, so that there is no need to worry about the memory resources not being released. If the pointer to the applied resource is handed over to the class for management (management: release resource), at the end of the function, the compiler automatically calls the destructor of the class to complete the release of the class management resources. In fact, the pointer is handed over to the class for management, the resource is applied for in the constructor, and the resource is released in the destructor.

  • Advantages
    No need to display the release of resources The
    object is always available when the resource is in use

Insert picture description here
At the same time, RAII also needs to make the class have a pointer-like way, only need to overload the two operators * and ->.
Disadvantages: RAII has a shallow copy problem.

template<class T>
class Smartptr
{
    
    
public:
	Smartptr(T *ptr = nullptr)
		:_ptr(ptr)
	{
    
    }
	~Smartptr()
	{
    
    
		if(_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

void testSmartptr()
{
    
    
	int *p = new int;
	Smartptr<int> sp(p);
}

Principles of smart pointers

The implementation method of RAII mentioned above cannot be regarded as a smart pointer, because it lacks several unique operations of the pointer.

  1. Dereference*
  2. Point to operation ->

Therefore, you only need to overload the above two operations in the RAII class.

template<class T>

class Smartptr
{
    
    
public:
	Smartptr(T *ptr = nullptr)
		:_ptr(ptr)
	{
    
    }
	~Smartptr()
	{
    
    
		if (_ptr)
			delete _ptr;
	}
	T& operator*() //指针所指向空间里面的内容
	{
    
    
		return *_ptr;
	}
	T* operator->()//返回的是指针的地址
	{
    
    
		return _ptr;
	}
private:
	T* _ptr;
};

struct Date
{
    
    
	int a;
	int b;
};
void testSmartptr()
{
    
    
	Smartptr<int> sp(new int);  //sp相当于一个对象,用来管理资源
	*sp = 10;
	cout << *sp << endl;
	Smartptr<Date> sp2(new Date);
	sp2->a = 1;
	sp2->b = 2;
	cout << sp2->a << sp2->b << endl;
}

The above method is not feasible in the copy constructor and assignment operator overloading method, and it is easy to produce shallow copies. However, deep copy cannot be used to solve the problem, because deep copy will generate two spaces. At the same time, resources are provided by external users. Smart pointers have no permission to apply for space and can only manage resources.

  • Principle of autoPtr in C++98

In the autoPtr library function, the realization principle is to transfer the content of the latter object to the previous object in the copy constructor and assignment operator overloading, and finally make the content of the latter object empty.

template<class T>
	class AutoPtr
	{
    
    
	public:
		AutoPtr(T* ptr)
			:_ptr(ptr)
		{
    
    }
		~AutoPtr()
		{
    
    
			if (_ptr)
				delete _ptr;
		}
		AutoPtr(AutoPtr<T>&sp) //拷贝构造函数
			:_ptr(sp._ptr)
		{
    
    
			sp._ptr = nullptr;
		}
		AutoPtr<T>& operator=(AutoPtr<T>&sp)
		{
    
    
			if (*this != sp)	//先判断是否自己给自己赋值
			{
    
    
				if (_ptr)  //如果赋值等号前面的有空间,需将其释放
					delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}
		AutoPtr* operator->()
		{
    
    
			return this;
		}
		AutoPtr& operator*()
		{
    
    
			return *this;
		}
	private:
		T* _ptr;
	};

Because only one copy can be used during deep copy and assignment, the C++ Standards Committee does not recommend it.

Improved version of auto_ptr:
Implementation principle: Add a parameter, bool owner, to manage the right to release resources.

if (_ptr && _owner) //资源存在,同时拥有资源释放的权利
					delete _ptr;
template<class T>
class auto_ptr
{
    
    
public:
	auto_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _owner(false)
	{
    
    	
		if (_ptr)
			_owner = true;
	}

	auto_ptr( auto_ptr<T>& p)
		:_ptr(p._ptr)
		,_owner(p._owner)
	{
    
    
		p._owner = false;
	}
	
	auto_ptr& operator*()
	{
    
    
		return *_ptr;
	}

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

	auto_ptr<T>& operator = (auto_ptr& p)
	{
    
    
		if (this != &p)
		{
    
    
			if (_ptr && _owner)
				delete _ptr;
			_ptr = p._ptr;
			_owner = p._owner;
			p._owner = false;
		}
		return *this;
	}

	~auto_ptr()
	{
    
    
		if (_ptr && _owner)
			delete _ptr;
	}
private: 
	T* _ptr;
	bool _owner;
};

The modified version of auto_ptr may cause wild pointers.

  • The unique_ptr library function, because smart pointers are prone to shallow copies, the overloading of the copy constructor and assignment operator is prohibited, and the copy constructor and assignment operator overloading are written as private member functions.
template<class T>
class  Uniqueptr
{
    
    
public:
	Uniqueptr(T* ptr = nullptr)
		:_ptr(ptr)
	{
    
    }
	~Uniqueptr()
	{
    
    
		if (_ptr)
			delete _ptr;[]
	}
	Uniqueptr* operator->()
	{
    
    
		return this;
	}
	Uniqueptr& operator*()
	{
    
    
		return *this;
	}
	Uniqueptr(Uniqueptr<T>const &) = delete;
	Uniqueptr<T>& operator = (Uniqueptr<T>const &) = delete;
private:
	
	T* _ptr;
};
  • shared_ptr
    is to realize the sharing of resources by counting. Judge whether it is necessary to release resources by counting, shared_ptr maintains a count for each resource, which is used to record that each resource is shared by several objects. When the object calls the destructor, the count is -1, and when the count is decremented When it reaches 0, it means that the last resource is not applicable, and the resource is finally released. When the count is not 0, it means that the resource is still in use and the resource cannot be released.

Insert picture description hereshared_ptr = RAII + operator* +opeartor-> + count

#include<mutex> //并发程序互斥锁 
template<class T>
class shared_ptr 
{
    
    
public:
	shared_ptr(T *ptr)
		:_ptr(ptr)
		,_pcount(new int(1))
		//,_pmutex(new mutex)
	{
    
    
	}
	~shared_ptr()
	{
    
    
		Release();
	}
	shared_ptr(const shared_ptr<T> &sp)
		:_ptr(sp._ptr)
		,_pcount(sp._pcount)
		//,_pmutex(sp._pmutex)
	{
    
    
		addcount();
	}
	shared_ptr<T>& operator=(const shared_ptr<T> &sp)
	{
    
    
		if (_ptr != sp._ptr)
		{
    
    
			Release(); //释放旧空间
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			addcount();			//计数+1
			//_pmutex = sp._pmutex;
		}
		return *this;
	}

	T& operator*()
	{
    
    
		return *this;
	}
	T* operator->()
	{
    
    
		return this;
	}

	void addcount()
	{
    
    
		//_pmutex->lock(); //加锁
		++(*_pcount);			//计数+1
		//_pmutex->unlock();//解锁
	}

	void Release()
	{
    
    
		//bool deleteflag = false;
		if (0 == --(*_pcount))
		{
    
    
			delete _ptr;
			delete _pcount;
		}
		/*if (deleteflag == ture)
		{
			delete _pmutex;
		}*/
	}

	int usecount()
	{
    
    
		return *_pcount;
	}
private:
	T * _ptr;
	int *_pcount;
	//mutex* _pmutex; 多线程加锁
};

Shared_ptr is improved. Due to shared_ptr, the managed pointer has multiple application methods, which makes it impossible to write the release function as fixed, so the destructor needs template specialization.

//定制删除器
template<class T>
class DFDel //默认new出来的空间
{
    
    
public:
	void operator()(T*& p)
	{
    
    
		if (p)
		{
    
    
			delete p;
			p = nullptr;
		}
	}
};

template<class T>
class Free //malloc申请的空间
{
    
    
public:
	void operator()(T*& p)
	{
    
    
		if (p)
		{
    
    
			free(p);
			p = nullptr;
		}
	}
};



class Fclose
{
    
    
public:
	void operator()(FILE* p)
	{
    
    
		if (p)
		{
    
    
			fclose(p);
			p = nullptr;
		}
	}
};

namespace bai
{
    
    
	template<class T,class DF = DFDel<T>>  //DF为删除类型,DF默认为new出来的空间
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    
			if (_ptr)
				_pcount = new int(1);
		}
		shared_ptr(const shared_ptr<T>& p)
			:_ptr(p._ptr)
			, _pcount(p._pcount)
		{
    
    
			if (_ptr) // 如果资源不为nullptr
				++(*_pcount);
		}

		//p1 == p2
		//p1:未管理资源------直接p2共享
		//p1:单独管理资源----在于p2共享之前先释放自己的资源
		//p1:与其他对象共享资源---p1计数--,p2计数++
		shared_ptr<T> operator = (const shared_ptr<T>& p)
		{
    
    
			if (this != &p)
			{
    
    
				if (_ptr && 0 == --(*_pcount))
				{
    
    
					delete _ptr;
					delete _pcount;
				}

				_ptr = p._ptr;
				_pcount = p._pcount;
				if (_ptr)
					++(*_pcount);
			}
			return *this;
		}


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

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


		~shared_ptr()
		{
    
    
			if (_ptr && --(*_pcount) == 0) //由于资源的申请方式不同,
			{
    
    							//所以需要根据资源的类型,定制释放的方式
				//delete _ptr;
				DF()(_ptr);				//DF相当于一种类型,DF()相当于创建一个无名的对象,DF()(_ptr),相当于DF对象调用
				delete _pcount;
			}
		}

		int usecount()
		{
    
    
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
}
void TestFunc()
{
    
    
	bai::shared_ptr<int,DFDel<int>> p1(new int);
	bai::shared_ptr<int,DFDel<int>> p2(p1);
	bai::shared_ptr<FILE, Fclose> p3(fopen("666.text" ,"rb")); 
}

The defect of shared_ptr:
When using smart pointers to manage doubly linked lists, circular references are prone to occur.

struct ListNode
{
    
    
	ListNode(int data = int())
	:_pre(nullptr)
	, _next(nullptr)
	{
    
    
		cout << "ListNode()" << this << endl;
	}

	~ListNode()
	{
    
    
		cout << "~ListNode()" << endl;
	}
	shared_ptr<ListNode> _pre;
	shared_ptr<ListNode> _next;
	int data;
};


void TestFunc()
{
    
    
	shared_ptr<ListNode> p1(new ListNode(10));
	shared_ptr<ListNode> p2(new ListNode(20));
	cout << p1.use_count() << endl;//查看p1中的引用计数
	cout << p2.use_count() << endl;
	p1->_next = p2;
	p2->_pre = p1;
	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;
}

Insert picture description here
The destructor is not called, which leads to resource leaks to
Insert picture description heresolve the shared_ptr circular reference
weak_ptr: resources cannot be managed separately, and must be used together with shared_ptr.
weak_ptr: It is to solve the circular reference that shared_ptr exists

struct ListNode
{
    
    
	ListNode(int _data = int())
		:data(_data)
	{
    
    
		cout << "ListNode()" << this << endl;
	}

	~ListNode()
	{
    
    
		cout << "~ListNode()" << endl;
	}
	weak_ptr<ListNode> _pre;  //****
	weak_ptr<ListNode> _next;
	int data;
};

Because weak_ptr cannot manage resources alone, the constructor cannot initialize _pre, _next
Insert picture description here

Guess you like

Origin blog.csdn.net/qq_42708024/article/details/102532950