Reference Counting

/*-----------------
	RefCountable
------------------*/
class RefCountable
{
	public:
		RefCountable() : _refCount(1) {}
		virtual ~RefCountable() {}

		int32 GetRefCount() { return _refCount; }
		int32 AddRef() { return ++_refCount; }
		int32 ReleaseRef()
		{
			int32 refCount = --_refCount;
			if (refCount == 0)
			{
				delete this;
			}
			return refCount;
		}
	protected:
		int32 _refCount;
};
class Wraight : public RefCountable
{
	public:
		int _hp = 150;
		int _posX = 0;
		int _posY = 0;
};

class Missile : public RefCountable
{
	public:
		void SetTarget(Wraight* target)
		{
			_target = target;
			target->AddRef();
		}

		bool Update()
		{
			if (_target == nullptr)
				return true;

			int posX = _target->_posX;
			int posY = _target->_posY;

			if (_target->_hp == 0)
			{
				_target->ReleaseRef();
				_target = nullptr;
				return true;
			}

			return false;
		}
		Wraight* _target = nullptr;
};

int main()
{
	Wraight* wraight = new Wraight();
	Missile* missile = new Missile();
	missile->SetTarget(wraight);

	// 레이스 피격
	wraight->_hp = 0;
	//delete wraight;
	wraight->ReleaseRef();
	wraight = nullptr;


	while (true)
	{
		if (missile)
		{
			if (missile->Update())
			{
				//delete missile;
				missile->ReleaseRef();
				missile = nullptr;
			}
		}
	}
}

객체가 다른 객체에 의해 참조되고있음에도 불구하고 삭제될 경우 문제가 발생할 수 있음
이를 해결하기 위해 _refCount를 통해 객체가 참조되고있는 횟수를 카운트
카운트가 0이 되었을때만 객체를 삭제하여 오류를 방지함

단, 현재의 _refCount를 증감하는 연산은 원자적으로 발생하지 않으므로 싱글 쓰레드에서만 정상적으로 동작
멀티쓰레드에서도 동일하게 작동하게 하려면 atomic 변수로 선언해주어야함
그러나 _target의 지정과 _refCount의 증감 역시 원자적으로 발생하지 않으므로 둘 사이에 ReleaseRef() 등이 실행될 경우 여전히 문제가 발생할 수 있음

/*--------------
	SharedPtr
---------------*/

template<typename T>
class TSharePtr
{
	public:
		TSharePtr() {}
		TSharePtr(T* ptr) { Set(ptr); }
		
		// 복사
		TSharePtr(const TSharePtr& rhs) { Set(rhs._ptr); }
		// 이동
		TSharePtr(TSharePtr&& rhs) { _ptr = rhs._ptr; rhs._ptr = nullptr; }
		// 상속 관계 복사
		template<typename U>
		TSharePtr(const TSharePtr<U>& rhs) { Set(static_cast<T*>(rhs._ptr)); }

		~TSharePtr() { Release(); }

	public:
		// 복사 연산자
		TSharePtr& operator=(const TSharePtr& rhs)
		{
			if (_ptr != rhs._ptr)
			{
				Release();
				Set(rhs._ptr);
			}
			return *this;
		}

		// 이동 연산자
		TSharePtr& operator=(TSharePtr&& rhs)
		{
			Release();
			_ptr = rhs._ptr;
			rhs._ptr = nullptr;
			return *this;
		}
		bool		operator==(const TSharePtr& rhs) const { return _ptr == rhs._ptr; }
		bool		operator==(T* ptr) const { return _ptr == ptr; }
		bool		operator!=(const TSharePtr& rhs) const { return _ptr != rhs._ptr; }
		bool		operator!=(T* ptr) const { return _ptr != ptr; }
		bool		operator<(const TSharePtr& 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:
		inline void Set(T* ptr)
		{
			_ptr = ptr;
			if (ptr)
				ptr->AddRef();
		}

		inline void Release()
		{
			if (_ptr != nulltpr)
			{
				_ptr->ReleaseRef();
				_ptr = nullptr;
			}
		}

	private:
		T* _ptr = nullptr;
};

따라서 스마트 포인터의 형식을 사용하여 문제를 해결
TSharedPtr이 어딘가에서 사용될경우 _refCount는 최소한 1 이상이 보장되기 때문에(객체가 존재하는 경우 Set()함수가 호출되어 카운트가 무조건 1 올라가게됨) 위에서와 같은 문제가 발생하지 않음
단, 모든 경우에 스마트 포인트를 사용해주어야 함
복사 대신 참조로 넘겨주어 _refCount를 증가시키지 않는 방법도 고려할 수 있음


스마트 포인터

스마트 포인터를 직접 만들어서 사용할 경우

  • 이미 만들어진 클래스 대상으로 사용 불가
  • 순환(Cycle) 문제 발생 (특히 상속관계인 경우)
    단, shared_ptr의 경우에도 순환 문제가 발생할 수 있음

표준 스마트 포인터에는 unique_ptr, shared_ptr, weak_ptr이 있음

unique_ptr

  • 일반 포인터와 비슷하나 복사가 막힘

shared_ptrweak_ptr

  • 사용하는 클래스 객체와 RefCountBlock 두가지의 메모리 영역을 가짐
  • RefCountBlock에는 useCountweakCount가 존재
    • useCount : shared_ptr로 참조하고있는 횟수
    • weakCount : weak_ptr로 참조하고있는 횟수

shared_ptr

  • shared_ptr<Knight> spr = make_shared<Knight>();과 같이 make_shared 사용시 객체와 RefCountingBlock을 하나로 합쳐서 할당함
  • UseCount가 0이 되면 객체가 제거되지만 weakCount가 남아있을경우 RefCountBlock은 제거하지 않음
  • shared_ptr끼리 서로 참조할경우 순환 문제 발생

weak_ptr

  • bool expired = wpr.expired(); 또는 shared_ptr<Knight> spr2 = wpr.lock(); 방식을 사용하여 존재 여부를 확인해야함
  • 객체 자체에는 영향을 주지 않음
  • 순환 문제를 방지할 수 있음

shared_ptrweak_ptr에 대한 자세한 내용