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 연산자를 호출하지 않아도 된다.