C++ Server - 멀티스레드 프로그래밍 (5)

창고지기·2025년 8월 26일
0

Cpp Server

목록 보기
5/6
post-thumbnail

Smart Pointer

사용자 정의 shared_pointer

RefCounting

  • 모든 객체가 RefCountable 클래스를 상속 받는다.
  • RefCountable 클래스에서 해당 객체의 참조 횟수를 저장한다.
  • 참조횟수가 0이되면 비로소 메모리에서 해제한다.
/*------------------
	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:
	atomic<int32> _refCount;
};
class Wraigth : public RefCountable
{
public:
	int32 _hp=150;
	int32 _x=0;
	int32 _y=0;
	 
};

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

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

		int x = _target->_x;
		int y = _target->_y;

		// 타겟이 중간에 없어지면 
        // 카운트를 줄이고 nullptr로 밀어줌
        // 만약 이 클래스만 참조하고 있었으면 _target이 가리키는 것은 메모리에서 해제 될 것
		if (_target->_hp == 0)
		{
			_target->ReleaseRef();
			_target = nullptr;
			return true;
		}

		return false;
	}

	Wraigth* _target = nullptr;
};

int main()
{

	//RefCount ver
	Wraigth* wraight = new Wraigth();
	Missile* missile = new Missile();
    // SetTarget 내부에서 refCnt를 증가시킨다.
	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;

TShared_ptr

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

// RefCountable 객체를 래핑해서 들고 있도록
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;
	}

	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; }
	T*				operator->() { return _ptr; }
	const T*		operator->() const { return _ptr; }
					// 캐스팅
					operator T* () 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 != nullptr)
		{
			_ptr->ReleaseRef();
			_ptr = nullptr;
		}
	}

private:
	T* _ptr = nullptr;
};
#include "RefCounting.h"
class Wraigth : public RefCountable
{
public:
	int32 _hp=150;
	int32 _x=0;
	int32 _y=0;
	 
};

//TShared Ver
using WraigthRef = TSharedPtr<Wraigth>;
using MissileRef = TSharedPtr<Missile>;

class Missile : public RefCountable
{
public:
	//void SetTarget(Wraigth* target)
	void SetTarget(WraigthRef target)
	{
		_target = target;
	}

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

		int x = _target->_x;
		int y = _target->_y;

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

		return false;
	}

	WraigthRef _target = nullptr;
};

int main()
{


	//TShared Ver
    // 여기서 cnt가 2임
    // new Wraigth()에서 1
    // WraigthRef wraight() 에서 1
	WraigthRef wraight(new Wraigth());
	// 소유권을 스마트 포인터에게 넘김
	wraight->ReleaseRef();
	MissileRef missile(new Missile());
	missile->ReleaseRef();

	//여기서 넘겨줄 때 복사가 발생
	//여기서 refcnt +1 하기 때문에 
	// 내가 사용하는 동안은 최소1, 삭제되지 않음
	missile->SetTarget(wraight);

	wraight->_hp = 0;
    
    //이게 동작하는 이유
    // wraight = WraigthRef(nullptr)
	wraight = nullptr;

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

		}
	}

	missile = nullptr;
}

STL smart_pointer

개념

modern C++에서 smart pointer는 프로그래머로 하여금 메모리, 자원의 누수로부터 자유롭게 해주고 예외에 안전한지 확인하는 데 도움을 주는 포인터로 (출처) auto_ptr(더이상 사용되지 않음), unique_ptr, shared_ptr, weak_ptr이 있다.

unique_ptr

  • 객체의 소유권을 가지도록 해주는 포인터로 기존 포인터에 대한 오버헤드가 거의 없다.
  • 기존 포인터와 유사하게 사용할 수 있지만, 복사가 막혀있음.
  • 진짜 복사를 원한다면 move를 통해서 소유권을 넘겨야함.
  • unique_ptr가 소멸되면 관리하던 객체를 delete

shared_ptr, weak_ptr

  • MSVC 기준 둘 다 _Ptr_base를 상속받는다.
    • _Ptr_base는 생성된 객체를 가리키는 _Ptr, _Ref_count_base를 가리키는 _Rep가 있다,
    • _Ref_count_base
      • 참조 카운트를 관리하기 위해서 사용
      • use, weak 변수를 가짐
      • use : shared 횟수
      • weak : weak_ptr 횟수
      • _ptr이 nullptr로 날아가더라도 weak가 0이 아니면 참조 카운트 블록을 유지
  • shared_ptr
    • 말 그래도 공유되는 포인터
    • 한 객체에 대해서 여러 shared_ptr 가능
    • new vs make_shared
      • new : 객체, 참조 카운트 블록을 따로따로 할당
      • make_shared : 한번에 할당
  • weak_ptr
    • 무언가 가리키려면 shared_ptr를 받아서 사용 해야함
    • .expired() or .lock()을 사용하여 유효한지 확인하고 사용
    • 소유하지 않고 관찰만 하는 입장이기 때문에 cycle 문제 해결 가능
#include <iostream>
#include <memoty>
using namespace std;
class Knight
{
public:
	Knight()
	{
		cout << "Knight()" << endl;
	}

	~Knight()
	{
		cout << "~Knight()" << endl;
	}
};

int main()
{
	/*------------------
		 unique_ptr
	------------------*/
	//일반 포인터와 유사 but 복사가 막혀있음
	//자동 해제 해줌
	unique_ptr<Knight> k2 = make_unique<Knight>();
	//unique_ptr<Knight> k3 = k2; //err
	unique_ptr<Knight> k3 = std::move(k2); //ok

	/*-------------------
	  shared_ptr & weak
	-------------------*/
	// weak의 경우 순환 해결 가능

	// [Knight][RefCountingBlock] (만들어진 메모리)
	
	// spr과 spr2가 동일한 메모리를 참조함
	// [T*][RefCountingBlock*] (메모리를 가리키는 포인터)
	shared_ptr<Knight> spr(new Knight());
	// [T*][RefCountingBlock*] (메모리를 가리키는 포인터)
	shared_ptr<Knight> spr2 = spr;
	
	// 위의 방법은 2개의 메모리를 따로따로 할당
	// 이 방법은 메모리를 한번에 할당
	shared_ptr<Knight> spr3 = make_shared<Knight>();


	//RefCountingBlock (use, weak로 구성)
	//use  shared로 참조하는 카운트
	//weak weak로 참조하는 카운트 (이게 0이 아니면 RefCountingBlock 계속 살아 있음)

	weak_ptr<Knight> wpr = spr3;

	// 자신이 유효한지 확인 후 사용
	// 둘중 하나를 사용
	bool expired = wpr.expired();
	shared_ptr<Knight> spr4 = wpr.lock();
}
profile
일단 창고에 넣어놓으면 언젠가는 쓰겠지

0개의 댓글