WIN32API FrameWork/원본

220504_WIN32API_Framework_4_스마트 포인터(참조 카운트)

hyrule 2022. 5. 6. 18:29

<일반 포인터>

 

 

<레퍼런스 카운팅>

 

* 추가 정보: https://hyrule.tistory.com/95

 

SharedPtr, Reference Counting 구현과 이해

//Class CRef //Ref.h /* [스마트 포인터] * 파이썬 또는 자바의 경우 가비지 컬렉터라는 프로그램이 계속 작동함. - 댕글링 포인터들을 자동으로 청소 but 속도가 느림. * 스마트 포인터 - 공유 포인터 -

hyrule.tistory.com

 

 

<추가한 내용>

1. 참조 카운트 클래스 - Ref.h, Ref.cpp

2. 공유 포인터 클래스 - SharedPtr.h

 

 

//Class CRef
//Ref.h


/*
[스마트 포인터]

* 파이썬 또는 자바의 경우 가비지 컬렉터라는 프로그램이 계속 작동함.
	- 댕글링 포인터들을 자동으로 청소 but 속도가 느림.

* 스마트 포인터 - 공유 포인터
	- 원래 기본 기능으로 지원하지만 게임엔진 등에서는
	직접 만들어서 쓰는 편이다.

* 어떤 주소를 3개의 포인터가 참조하다가 한 포인터에서 delete를 했을 시
나머지 2개 포인터는 댕글링 포인터가 되어 버린다. 매우 위험한 상황.

* 그래서 레퍼런스 카운터라는 방법을 사용한다.
	- 모든 주소는 RC라는 값을 가지고 있고,
	- delete를 할때 RC값이 0이 아니면 할당을 해제하는 것이 아니라
	RC값을 하나 줄인다.
	- 위의 상황에서 한 포인터에서 delete를 할 경우에
	주소가 할당 해제가 되는 것이 아니라 RC가 2가 되는 방식이다.
*/


#pragma once

#include "GameInfo.h"

class CRef
{
public:
	CRef();
	CRef(const CRef& ref);
	virtual ~CRef();



public:
	//참조 카운트수 증가/감소 및 0이면 delete해주는 함수 추가
	void AddRef();
	void Release();

protected:
	int m_RefCount;

	//cf)size_t = unsigned __int64
	size_t	m_TypeID;
	std::string m_Name;
	std::string m_TypeName;
	bool m_Enable; //활성/비활성
	bool m_Active; //살아있는지 죽었는지

public:
	size_t	GetTypeID()	const
	{
		return m_TypeID;
	}

	const std::string& GetTypeName() const
	{
		return m_Name;
	}

	//같은 타입인지 체크
	template <typename T>
	bool CheckTypeID() const
	{
		return m_TypeID == typeid(T).hash_code();
	}

	template <typename T>
	void SetTypeID()
	{
		//타입 이름을 문자열 형태로 받아온다.
		//int ->"int",class A -> "class A"
		m_TypeName = typeid(T).name();

		//각 타입은 고유 번호를 가지고 있다. 이 번호를 가져온다.
		m_TypeID = typeid(T).hash_code();
	}
	void SetName(const std::string& name)
	{
		m_Name = name;
	}

	bool GetEnable() const
	{
		return m_Enable;
	}
	void SetEnable(bool Enable)
	{
		m_Enable = Enable;
	}

	bool GetActive() const
	{
		return m_Active;
	}

	void SetActive(bool Active)
	{
		m_Active = Active;
	}

};
//Class CRef
//Ref.cpp


#include "Ref.h"

CRef::CRef() :
	m_RefCount(0),
	m_Enable(true),
	m_Active(true),
	m_TypeID(0)
{
}

CRef::CRef(const CRef& ref):
	m_RefCount(0),
	m_TypeName(ref.m_TypeName),
	m_TypeID(ref.m_TypeID),
	m_Enable(ref.m_Enable),
	m_Active(ref.m_Active)
{
}

CRef::~CRef()
{
}

void CRef::AddRef()
{
	++m_RefCount;
}

void CRef::Release()
{
	--m_RefCount;


	//참조 수가 없다고 확인되면 자기 자신을 삭제
	if (m_RefCount <= 0)
	{
		//자기 자신을 삭제하는 함수는 문제가 되지 않는다.
		//함수는 만들어진 객체가 없어도 존재하기 때문.
		//클래스 포인터가 nullptr이어도 함수는 호출이 가능하다.
		//클래스 일반 멤버변수가 들어오면 문제가 발생하지만
		//그렇지 않으면 문제가 되지 않는다.
		delete this;
		return;
	}
}

 

 

 

 

//SharedPtr.h

#pragma once

/*
[SharedPtr.h]
* Ref(참조 카운팅) 클래스와 같이 활용하는 클래스
* 앞으로 포인터 변수를 직접 만드는 대신, SharedPtr 클래스를 생성한다.
* 

*/

template <typename T>
class CSharedPtr
{
public:
	CSharedPtr():
		m_Ptr(nullptr)
	{

	}

	//복사 생성자
	CSharedPtr(const CSharedPtr<T>& ptr)
	{
		m_Ptr = ptr.m_Ptr;

		if (m_Ptr)
			m_Ptr->AddRef();
	}

	CSharedPtr(T* ptr)
	{
		m_Ptr = ptr;

		if (m_Ptr)
			m_Ptr->AddRef();
	}


	~CSharedPtr()
	{
		if (m_Ptr)
			m_Ptr->Release();
	}

private:
	T* m_Ptr;


public:

	//기존에 포인터를 가지고 있던 주소에 새로운 주소를 '대입'
	//클래스 형태로 비교를 할 수도 있고
	//아예 포인터 주소를 비교할 수도 있으므로
	void operator = (const CSharedPtr<T>& ptr)
	{
		//기존에 참조하고 있던 객체가 있을 경우 카운트를 1 감소한다.
		if (m_Ptr)
			m_Ptr->Release();


		//새로운 주소를 등록
		m_Ptr = ptr.m_Ptr;

		//m_Ptr이 nullPtr이 아닐 경우 자신을 레퍼런스 카운트팅을 늘려준다.
		if (m_Ptr)
		{
			m_Ptr->AddRef();
		}
	}
	void operator = (T* ptr)
	{
		if (m_Ptr)
			m_Ptr->Release();

		m_Ptr = ptr;

		if (m_Ptr)
			m_Ptr->AddRef();
	}

	bool operator == (const CSharedPtr<T>& ptr) const
	{
		return m_Ptr == ptr.m_Ptr;
	}
	bool operator == (T* ptr) const
	{
		return m_Ptr == ptr;
	}

	bool operator != (const CSharedPtr<T>& ptr) const
	{
		return m_Ptr != ptr.m_Ptr;
	}
	bool operator != (T* ptr) const
	{
		return m_Ptr != ptr;
	}


	operator T* () const
	{
		return m_Ptr;
	}

	T* operator ->() const
	{
		return m_Ptr;
	}

	T* operator * () const
	{
		return m_Ptr;
	}

	T* Get() const
	{
		return m_Ptr;
	}
};

 

 

 

 

<사용법>

* 앞으로 참조 카운트 방식을 사용할 클래스는 Ref 클래스를 부모로 상속받아 준다.

* 포인터 변수를 생성하는 대신 SharedPtr<T> ptr = new T;와 같이 공유 포인터 클래스에 생성하고 주소를 저장한다.

* SharedPtr 클래스가 소멸될 때 레퍼런스 카운트를 감소시키고, 각자 클래스에서 레퍼런스 카운트가 0이 되면 알아서 자기 자신의 메모리 할당을 해제한다. 앞으로는 delete 연산자를 호출하지 않아도 된다.