shared_ptr 구현해보기
※ 개인적인 공부를 위해 포스팅 하는 글입니다.
스마트 포인터 사용 이유
class Knight
{
public:
Knight() { cout << "나이트생성" << endl; }
~Knight() { cout << "나이트소멸" << endl; }
void Attack()
{
if (_target)
{
_target->_hp -= _damage;
cout << "HP: " << _target->_hp << endl;
}
}
public:
int _hp = 100;
int _damage = 10;
Knight* _target = nullptr;
};
int main()
{
Knight* k1 = new Knight();
Knight* k2 = new Knight();
k1->_target = k2;
delete k2;
k1->Attack();
return 0;
}
ㆍ k2유저가 접속을 종료해서 삭제해 줬는데 k1이 공격을 했다.... 일반 원시포인터는 사람이 실수할 여지가 있다...
★ 솔찍히 요즘 언리얼같은 현대 C++을 사용하면 원시포인터보단 스마트포인터를 많이 쓰게된다.
개념 포스팅 링크: https://season97.tistory.com/32
쓰레드, 코루틴, 델리게이트와 스마트포인터
▶ 쓰레드란? ㆍ 프로세스 내에서 실행되는 가장 작은 실행 단위 ㆍ 프로세스와 메모리 자원을 공유하며, 여러 쓰레드가 하나의 프로스세스 내에서 동시에 수행될 수 있다. ▶ 쓰레드의 주요
season97.tistory.com
▶ 스마트 포인터 ◀
포인터를 알맞는 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)
shared_ptr
weak_ptr
unique_ptr
언리얼에선 다른 이름으로 래핑되어있다.
1. shared_ptr
ㆍ 내부적으로 참조 카운팅 방식을 사용하여 몇 개의 shared_ptr이 해당 객체를 참조하고 있는지 추적한다.
ㆍ 참조 카운트는 shared_ptr 이 생성될때나 다른 shared_ptr에 복사될 때 증가한다.
ㆍ 참조 카운트는 shared_ptr이 소멸되거나 다른 객체를 가리킬때 감소한다.
ㆍ 참조 카운트가 0이 되면 (즉 마지막 shared_ptr이 사라지면) 자동으로 해당 객체를 삭제한다
# 템플릿으로 구현해보기
ㆍ 실제 내부적으로는 다르겠지만 shared_ptr처럼 동작하게 구현해봤다.
class RefCountBlock
{
public:
int _refCount = 1;
};
template<typename T>
class SharedPtr
{
public:
SharedPtr() {}
SharedPtr(T* ptr) : _ptr(ptr)
{
if (_ptr != nullptr)
{
_block = new RefCountBlock();
cout << "참조카운트 : " << _block->_refCount << endl;
}
}
SharedPtr(const SharedPtr& sptr) : _ptr(sptr._ptr), _block(sptr._block)
{
if (_ptr != nullptr)
{
_block->_refCount++;
cout << "참조카운트 : " << _block->_refCount << endl;
}
}
void operator=(const SharedPtr& sptr)
{
_ptr = sptr._ptr;
_block = sptr._block;
if (_ptr != nullptr)
{
_block->_refCount++;
cout << "참조카운트 : " << _block->_refCount << endl;
}
}
~SharedPtr()
{
if (_ptr != nullptr)
{
_block->_refCount--;
cout << "참조카운트 : " << _block->_refCount << endl;
if (_block->_refCount == 0)
{
delete _ptr;
delete _block;
cout << "데이터 삭제" << endl;
}
}
}
public:
T* _ptr = nullptr;
RefCountBlock* _block = nullptr;
};
int main()
{
SharedPtr<Knight> k1(new Knight());
SharedPtr<Knight> k2 = k1;
return 0;
}
ㆍ 잘 작동한다. 객체가 생성될때 참조카운트가 1이 되고 복사생성자, 복사 대입연산자 호출될때 참조카운트가 증가한다.
ㆍ 스마트 포인터를 사용하면 이제 알아서 메모리를 날려주는 장점이 생긴다.
int main()
{
// SharedPtr<Knight> k1(new Knight());
//SharedPtr<Knight> k2 = k1;
shared_ptr<Knight> k1 = make_shared<Knight>();
{
shared_ptr<Knight> k2 = make_shared<Knight>();
k1->_target = k2;
}
k1->Attack();
return 0;
}
ㆍ 처음 실행한 코드도 이제는 잘 작동한다. 스코프를 벗어나서 참조카운트가 깎였을 뿐 완전히 소멸된 것은 아니다.
ㆍ 모던cpp부턴 원시포인터 거의 사용 안한다고 보면된다.