C ++ --- 스마트 포인터의 원리 설명

스마트 포인터가 필요한 이유

코드 작성시 힙에서 적용된 공간이 일부 이유 (코드 중간에 비정상)로 인해 해제되지 않아 코드에서 메모리 누수가 발생했기 때문에 이러한 문제를 방지하기 위해 스마트 포인터가 등장했습니다. . 스마트 포인터를 사용하면 포인터가 사용되지 않는 경우 자동으로 감지 할 수 있으며 힙의 공간이 자동으로 해제됩니다.

스마트 포인터의 역할 포인터를
관리하고 리소스를 자동으로 해제합니다.
여기에 사진 설명 삽입

RAII

RAII는 개체 수명주기를 사용하여 프로그램 리소스를 제어하는 ​​기술입니다.

클래스에서는 객체가 생성 될 때 자원을 획득하고 마지막으로 객체가 파괴 된 후 자원이 해제되므로 메모리 자원이 해제되지 않을까 걱정할 필요가 없습니다. 적용된 리소스에 대한 포인터가 관리를 위해 클래스 (관리 : 리소스 해제)로 넘겨지면 함수의 끝에서 컴파일러가 클래스의 소멸자를 자동으로 호출하여 클래스 관리 리소스의 해제를 완료합니다. 실제로 포인터는 관리를 위해 클래스로 넘겨지고 리소스는 생성자에서 적용되고 리소스는 소멸자에서 해제됩니다.

  • 장점
    리소스 릴리스를 표시 할 필요가 없으며
    개체는 항상 사용 중에 유효합니다.

여기에 사진 설명 삽입
동시에 RAII는 클래스를 포인터와 같은 방식으로 만들어야하며 두 연산자 * 및-> 만 오버로드하면됩니다.
단점 : RAII에는 얕은 복사 문제가 있습니다.

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

스마트 포인터의 원리

위에서 언급 한 RAII의 구현 방법은 포인터의 몇 가지 고유 한 동작이 없기 때문에 스마트 포인터로 간주 할 수 없습니다.

  1. 역 참조 *
  2. 작동 지점->

따라서 RAII 클래스에서 위의 두 작업 만 오버로드하면됩니다.

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

위의 방법은 복사 생성자 및 할당 연산자 오버로딩 방법에서 실행 가능하지 않으며 얕은 복사본을 생성하기 쉽습니다. 그러나 딥 카피는 2 개의 스페이스를 생성하기 때문에 문제를 해결하는 데 사용할 수 없습니다. 동시에 외부 사용자가 리소스를 제공합니다. 스마트 포인터는 스페이스 신청 권한이 없으며 리소스 만 관리 할 수 ​​있습니다.

  • C ++ 98에서 autoPtr의 원리

autoPtr 라이브러리 함수에서 실현 원칙은 복사 생성자 및 할당 연산자 오버로딩에서 후자 개체의 내용을 이전 개체로 전송하고 마지막으로 후자 개체의 내용을 비우는 것입니다.

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

딥 복사 및 할당에는 하나의 복사본 만 사용할 수 있으므로 C ++ 표준위원회에서는 권장하지 않습니다.

auto_ptr의 개선 된 버전 :
구현 원칙 : 리소스 해제 권한을 관리하기 위해 bool owner 매개 변수를 추가합니다.

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

auto_ptr의 수정 된 버전으로 인해 와일드 포인터가 발생할 수 있습니다.

  • unique_ptr 라이브러리 함수는 스마트 포인터가 얕은 복사에 취약하기 때문에 복사 생성자 및 할당 연산자의 오버로딩이 금지되고 복사 생성자 및 할당 연산자 오버로딩이 전용 멤버 함수로 작성됩니다.
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
    은 세어 자원의 공유를 실현하는 것입니다. 개수를 계산하여 자원 해제가 필요한지 판단하면 shared_ptr은 각 자원에 대한 개수를 유지하여 각 자원이 여러 객체에서 공유되고 있음을 기록하는 데 사용됩니다. 객체가 소멸자를 호출하면 개수는 -1이고 개수가 0에 도달하면 마지막 자원을 적용 할 수없고 자원이 최종적으로 해제됨을 의미합니다. 개수가 0이 아니면 리소스가 아직 사용 중이며 리소스를 해제 할 수 없음을 의미합니다.

여기에 사진 설명 삽입shared_ptr = RAII + 연산자 * + opeartor-> + 개수

#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이 개선되었습니다 .shared_ptr로 인해 관리 포인터에는 여러 응용 프로그램 메서드가있어 릴리스 함수를 고정 된 것으로 작성할 수 없으므로 소멸자는 템플릿 전문화가 필요합니다.

//定制删除器
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")); 
}

shared_ptr의 결함 :
스마트 포인터를 사용하여 이중 연결 목록을 관리 할 때 순환 참조가 발생하기 쉽습니다.

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

여기에 사진 설명 삽입
소멸자가 호출되지 않아
여기에 사진 설명 삽입shared_ptr 순환 참조
weak_ptr 해결하기 위해 리소스 누수가 발생합니다 . 리소스는 별도로 관리 할 수 ​​없으며 shared_ptr과 함께 사용해야합니다.
weak_ptr : shared_ptr이 존재하는 순환 참조를 해결하는 것입니다.

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

weak_ptr은 리소스를 단독으로 관리 할 수 ​​없기 때문에 생성자는 _pre, _next를 초기화 할 수 없습니다.
여기에 사진 설명 삽입

추천

출처blog.csdn.net/qq_42708024/article/details/102532950