Reference Counting

Kimbab1004·2024년 1월 27일
0

Server

목록 보기
1/7
post-thumbnail

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 사용했을 때의 문제가 생기지 않는 이유는?

  • 래핑 클래스(TSharedPtr)가 생성, 소멸 주기를 대신 관리하기 때문.
  • 함수 파라메타로 전달할 때, 복사 생성자를 부르면서 최소한 1의 refcount는 유지하기 때문.
/*-----------------
	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;
}

0개의 댓글