오븐 노트

[C++] 스마트 포인터 (smart pointer) 본문

Develop/C++

[C++] 스마트 포인터 (smart pointer)

오 븐 2024. 1. 4. 20:38
#include <iostream>
using namespace std;
#include <vector>
#include <list>
#include <deque>
#include <map>
#include <set>
#include <algorithm>

class Knight
{
public:
	Knight() { cout << "Knight 생성" << endl; }
	~Knight() { cout << "Knight 소멸" << endl; }
	
	void Attack()
	{
		if (_target.expired() == false) // weak_ptr로 인해 사용되는 expired. 해당 포인터가 살아있는가. false이면 유효
		{
			shared_ptr<Knight> sptr = _target.lock();

			sptr-> _hp -= _damage;
			cout << "HP: " << sptr->_hp << endl;
		}
	}

public:
	int _hp = 100;
	int _damage = 10;
	weak_ptr<Knight> _target; // weak_ptr는 Knight의 소멸에 관여하지않기에 순환 구조가 일어날 수 없게 되지만, 번거롭다는 단점이 있다
	//Knight* _target = nullptr; // 현대적인 C++에서는 이런식으로 포인터를 직접적으로 사용하지 않는다.
};

class RefCountBlock
{
public:
	int _refCount = 1; // 해당 객체를 참조하는 것이 몇개인지
	int _weakCount = 1;
};

template<typename T>
class SharedPtr
{
public:
	SharedPtr() { }
	SharedPtr(T* ptr) : _ptr(ptr)
	{
		if (_ptr != nullptr)
		{
			_block = new RefCountBlock();
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	SharedPtr(const SharedPtr& sptr) : _ptr(sptr._ptr), _block(sptr._block)
	{
		if (_ptr != nullptr)
		{
			_block->_refCount++;
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	void operator=(const SharedPtr& sptr)
	{
		_ptr = sptr._ptr;
		_block = sptr._block;

		if (_ptr != nullptr)
		{
			_block->_refCount++;
			cout << "RefCount : " << _block->_refCount << endl;
		}
	}

	~SharedPtr()
	{
		if (_ptr != nullptr)
		{
			_block->_refCount--; // _refCount를 감소 시키고 당장 delete하지 않는것이 포인트
			cout << "RefCount : " << _block->_refCount << endl;

			if (_block->_refCount == 0) // 한번 검증 한 후 아무도 사용하지 않는다는것을 확인하고 삭제
			{
				delete _ptr;
				delete _block;
				cout << "Delete Data" << endl;
			}
		}
	}

public:
	T* _ptr = nullptr;
	RefCountBlock* _block = nullptr;
};

int main()
{
	// -------생 포인터를 사용할 때-------
	/*Knight* k1 = new Knight();
	Knight* k2 = new Knight();

	k1->_target = k2;

	delete k2; // k2가 바로 삭제되어 k1이 가리키는 값이 엉뚱해져 버린다!

	k1->Attack();*/

	// 스마트 포인터 : 포인터를 알맞는 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)
	// shared_ptr(대표), weak_ptr(shared의 단점을 메꾸기 위해 활용), unique_ptr(아예 별도)
	
	// -------shared_ptr 구현부-------
	//SharedPtr<Knight> k2;
	//{
	//	SharedPtr<Knight> k1(new Knight());
	//	k2 = k1; // k1과 k2 모두 같은 객체를 참조하고 있는 상태이기에 삭제한다고 바로 삭제되지 않는다(shared_ptr)
	//}

	// -------shared_ptr 활용-------
	//shared_ptr<Knight> k1 = make_shared<Knight>(); // new Knight()를 넣어줘도 일단은 되기는 하지만, 해당 버전이 성능이 좀 더 좋다 -> 메모리 블럭을 한 번에 만들어 줌
	//{
	//	shared_ptr<Knight> k2 = make_shared<Knight>();
	//	k1->_target = k2; // 이미 날아간 메모리를 참조하는 dangling 문제 또는 use after free와 같은 문제에 대해서 일단 어느정도 자유로워진다는 장점
	//} // 서버쪽에서는 특히나 멀티쓰레드가 되면 복잡해지기에 shared_ptr이나 자체 제작한 스마트 포인트를 활용한다.
	//  // 모던 C++부터는 생 포인터를 어지간하면 사용하지 않는다
	//k1->Attack();

	// -------weak_ptr를 사용하는 이유-------
	shared_ptr<Knight> k1 = make_shared<Knight>();
	
	// k1 [ refCount 2]
	// k2 [ refCount 1] // 서로 주시하고 있기에 어떠한 상황이든 절대로 메모리가 소멸되지 않는 이슈가 발생
	shared_ptr<Knight> k2 = make_shared<Knight>();
	k1->_target = k2;
	k2->_target = k1;

	k1->Attack();

	// -------shared_ptr만으로 만들 때 문제점-------
	//k1->_target = nullptr; // 소멸할때쯤 밀어줘야한다
	//k2->_target = nullptr; // ex) 유저와 몹이 서로 인식하여 메모리가 순환되어버림

	unique_ptr<Knight> uptr = make_unique<Knight>(); // 말 그대로 세상에 혼자만 가지고 있어야하는 포인터
	unique_ptr<Knight> uptr2 = std::move(uptr); // 때문에 이동론을 이용하여 소유권 자체를 넘기지 않는 한 넘길 수 없다

	return 0;
}

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 - 인프런 | 강의
 
C++ 카테고리의 글은 인프런 Rookiss님의 강의를 공부하며 정리하는 내용입니다.
이미 알고 있는 내용도 다시 정리 되어있을 수 있습니다.

 

모든 글은 제가 공부하기 위해 작성합니다.

'Develop > C++' 카테고리의 다른 글

[C++] 람다 (lambda)  (0) 2024.01.02
[C++] 전달 참조 (forwarding reference)  (0) 2024.01.02
[C++] 오른값 참조 (rvalue reference)  (0) 2023.12.30
[C++] override, final  (0) 2023.12.28
[C++] delete(삭제된 함수)  (0) 2023.12.13