Pointer를 이용하던 중 Pointer의 삭제로 인해 생기는 문제가 생길 수 있다.
특정 Pointer를 이용하던중 해당 Pointer가 사라졌을때 다른 Function에서 해당 Pointer를 지속해서 사용 하고 있을 시 Crash가 일어나지 않고 GarbageData를 이용해 사용하는 경우가 있다.
이를 해결하기 위해 Referenct Counting을 사용한다.
Reference Counting이란 해당 객체를 참조하는 Pointer의 수를 추적하고, 이 수가 0이 되면 해당 객체를 자동으로 삭제하는 메커니즘이다.
이를 통해 위와 같은 상황이 발생시에도 GarbageData를 사용하지 않고 본래 메모리를 참조해 제대로 기능을 구현 할 수 있을 것이다. 하지만 Reference Counting에도 단점은 존재한다.
Multi Thread 환경에서는 제대로 작동되지 않는다는 점이다. Reference Counting을 통해 다수의 Pointer 구현간 문제가 없더라도 만약 Multi Thread로 갈 시 Writer간 문제가 발생 할 수 있기 때문이다.
Atomic 연산을 사용하면 해결 되는 문제가 아닌가?
기존 Multi Thread에서 발생하는 여러 문제를 해결하기 위해 원자적 연산 방법을 이용해 해결해 온 것은 맞지만 이는 근본적인 해결책은 아니다. 다른 쓰레드간의 Race Condition은 예방 될지라도 내부적으로 성능 오버헤드, Double-Checked Locking 문제가 발생 할 수 있기 때문이다.
그렇다면 Multi Thread 환경에서 Reference Counting은 어떻게 해결해야 하는 것일까?
방법은 TSharedPtr(Smart Pointer)를 이용하는 것이다.
TSharedPtr은 Unreal Engine에서 사용되는 스마트 포인터(Smart Pointer) 중 하나로, C++의 std::shared_ptr와 유사한 기능을 제공한다. Unreal Engine은 자체적으로 스마트 포인터를 구현하며, 이를 TSharedPtr라는 템플릿 클래스로 제공한다.
TSharedPtr 사용하면 RefCounting 사용했을 때의 문제가 생기지 않는 이유는?
/*-----------------
SharedPtr
-----------------*/
template<typename T>
class TSharedPtr
{
public:
TSharedPtr() {}
TSharedPtr(T* ptr) { Set(ptr); }
// 복사
TSharedPtr(const TSharedPtr& rhs) { Set(rhs._ptr); }
// 이동
TSharedPtr(TSharedPtr&& rhs) { _ptr = rhs._ptr; rhs._ptr = nullptr; }
// 상속 관계 복사
template<typename U>
TSharedPtr(const TSharedPtr<U>& rhs) { Set(static_cast<T*>(rhs._ptr)); }
~TSharedPtr() { Release(); }
public:
// 복사 연산자
TSharedPtr& operator=(const TSharedPtr& rhs)
{
if (_ptr != rhs._ptr)
{
Release();
Set(rhs._ptr);
}
return *this;
}
// 이동 연산자
TSharedPtr& operator=(TSharedPtr&& rhs)
{
Release();
_ptr = rhs._ptr;
rhs._ptr = nullptr;
return *this;
}
bool operator==(const TSharedPtr& rhs) const { return _ptr == rhs._ptr; }
bool operator==(T* ptr) const { return _ptr == ptr; }
bool operator!=(const TSharedPtr& rhs) const { return _ptr != rhs._ptr; }
bool operator!=(T* ptr) const { return _ptr != ptr; }
bool operator<(const TSharedPtr& rhs) const { return _ptr < rhs._ptr; }
T* operator*() { return _ptr; }
const T* operator*() const { return _ptr; }
operator T* () const { return _ptr; }
T* operator->() { return _ptr; }
const T* operator->() const { return _ptr; }
bool IsNull() { return _ptr == nullptr; }
private:
void Set(T* ptr)
{
_ptr = ptr;
if (ptr)
ptr->AddRef();
}
void Release()
{
if (_ptr != nullptr)
{
_ptr->ReleaseRef();
_ptr = nullptr;
}
}
<Main>
private:
T* _ptr = nullptr;
};
using WraightRef = TSharedPtr<Wraight>;
class Missile : public RefCountable
{
public:
void SetTarget(WraightRef target)
{
_target = target;
//target->AddRef();
}
bool Update()
{
if (_target == nullptr) { return true; }
int posX = _target->_posX;
int posY = _target->_posY;
// TODO 쫓아가기
if (_target->_hp == 0)
{
//_target->ReleaseRef();
_target = nullptr;
return true;
}
return false;
}
WraightRef _target = nullptr;
};
using MissileRef = TSharedPtr<Missile>;
int main()
{
WraightRef wraight(new Wraight());
wraight->ReleaseRef();
MissileRef missile(new Missile());
missile->ReleaseRef();
missile->SetTarget(wraight);
// 레이스가 피격 당함
wraight->_hp = 0;
//wraight->ReleaseRef();
wraight = nullptr;
while (true)
{
if (missile)
{
if (missile->Update())
{
//missile->ReleaseRef();
missile = nullptr;
}
}
}
//missile->ReleaseRef();
missile = nullptr;
}