오늘부터 여러번에 걸쳐 스마트 포인터 중에서도 shared_ptr에 대해서 자세하게 다뤄볼 예정입니다.
shared_ptr을 직접 구현할 작업이 있어 해보다 재미있는 내용이 많아 포스팅 해 보려고 합니다.
[ shared_ptr ? ]
shared_ptr에 관한 설명을 cpp reference에서 가져온 내용입니다.
이런식으로 설명이 되어 있습니다.
std::shared_ptr은 포인터를 통하여 object의 공유된 소유권을 유지합니다.
여러 shared_ptr object는 같은 object를 가지고 있습니다.
그리고 그 오브젝트는 다음이 일어날 때 파괴가 됩니다.
- 오브젝트의 소유권을 가진 마지막 shared_ptr 이 삭제될 때.
- 오브젝트의 소유권을 가진 마지막 shared_ptr이 다른 포인터로 할당이 될 때. (reset이나 operator=를 통하여)
저장된 포인터는 get()함수에 의해 접근이 가능하다.
shared_ptr은 no objects상태가 있는데, 즉 아무 object도 소유하지 않은 상태가 있는데, 이 상태는 empty라고 부릅니다.
모든 member함수는 동기화 없이 멀티 스레드에서 불릴 수 있고, 다른 인스턴스의 shared_ptr에서 불릴 수 있다.
참고 : https://en.cppreference.com/w/cpp/memory/shared_ptr
이런 내용으로 되어 있습니다.
[ shared_ptr.h ]
헤더 구현부입니다.
std::shared_ptr 내부를 보며 직접 구현하였습니다.
이름은 최대한 std를 따라 만들었습니다 !
shared_ptr은 refCount와 실제 객체인 ptr로 구성되어 있고, 둘다 포인터로 되어있는것이 포인트입니다.
껍데기가 동적할당으로 되어있지 않은 shared_ptr이 소멸될 때, refCount가 소멸되지 않도록 동적할당이 되어 있습니다.
#pragma once
template <typename TYPE>
class eb_shared_ptr
{
public:
eb_shared_ptr() ;
explicit eb_shared_ptr(TYPE* ptr) ;
eb_shared_ptr(const eb_shared_ptr<TYPE>& rhs) ;
~eb_shared_ptr() ;
void set(TYPE* ptr) noexcept;
TYPE* get() const noexcept { return _ptr; };
int use_count() const noexcept { return (*_refCount); };
bool unique() const noexcept { return { _refCount ? (*_refCount == 0) : false}; }
TYPE* operator->() { return _ptr; };
TYPE& operator* () { return *_ptr; };
operator bool() const { return (nullptr != _refCount); }
eb_shared_ptr<TYPE>& operator=(const eb_shared_ptr<TYPE>& rhs);
private:
void addRef() noexcept;
void release() noexcept;
private:
int* _refCount;
TYPE* _ptr;
};
[ shared_ptr.hpp ]
hpp 파일입니다.
생성자, 소멸자 부분 구현은 다음과 같습니다.
기본 생성자는 처음에 만들지 않으려고 했으나 제한되는 경우가 너무 많아서.. (예를들면 vector reserve 이용시) 만들었습니다. 기본생성자만 있는 경우에 std::shared_ptr 에서 empty상태가 될 것 같습니다.
refCount할당은 한번에 몰아서 하기 위해 addRef 함수로 빼 두었습니다.
#include "eb_shared_ptr.h"
template<typename TYPE>
eb_shared_ptr<TYPE>::eb_shared_ptr()
: _refCount(nullptr)
, _ptr(nullptr)
{
}
template<typename TYPE>
eb_shared_ptr<TYPE>::eb_shared_ptr(TYPE* ptr)
: _refCount(nullptr)
, _ptr(ptr)
{
addRef();
}
template<typename TYPE>
eb_shared_ptr<TYPE>::eb_shared_ptr(const eb_shared_ptr<TYPE>& rhs)
: _refCount(rhs._refCount)
, _ptr(rhs._ptr)
{
addRef();
}
template<typename TYPE>
eb_shared_ptr<TYPE>::~eb_shared_ptr()
{
release();
}
addRef , release 함수는 private로 생성했습니다.
refCount가 없는 경우 크래시 가능성이 있어 _refCount null 체크를 다 해주어야 합니다.
template<typename TYPE>
void eb_shared_ptr<TYPE>::addRef() noexcept
{
if (nullptr == _refCount)
{
_refCount = new int(0);
}
(*_refCount)++;
}
template<typename TYPE>
void eb_shared_ptr<TYPE>::release() noexcept
{
if (0 == --(*_refCount))
{
delete _refCount;
delete _ptr;
}
}
template<typename TYPE>
void eb_shared_ptr<TYPE>::set(TYPE* ptr) noexcept
{
//원래 있던 객체 해제
if (_refCount)
{
release();
_refCount = nullptr;
_ptr = nullptr;
}
addRef();
_ptr = ptr;
}
template<typename TYPE>
eb_shared_ptr<TYPE>& eb_shared_ptr<TYPE>::operator=(const eb_shared_ptr<TYPE>& rhs)
{
_refCount = rhs._refCount;
_ptr = rhs._ptr;
addRef();
}
[ main ]
실행결과 입니다.
shared1이 생성되고 shared2에 shared1을 카피해서 넣으면서 복사 생성자가 불리게 됩니다.
refcount는 2가 됩니다.
스코프를 생성되면서 shared3이 shared2로 생성됩니다. (refCount = 3)
스코프를 벗어나면서 shared3이 사라지면서 refCount는 다시 2가 됩니다.
using namespace std;
int main(void)
{
eb_shared_ptr<int> shared1(new int(0));
eb_shared_ptr<int> shared2 = shared1;
int count = shared1.use_count();
cout << count << endl;
{
eb_shared_ptr<int> shared3 = shared2;
int count2 = shared3.use_count();
cout << count2 << endl;
}
int count3 = shared2.use_count();
cout << count3 << endl;
return 0;
}
shared_ptr이 thread safety 한것으로 알고 사용하였으나, 그렇지 않다는 부분을 구현하면서 알게 되었습니다.
다음시간에는 thread safety shared_ptr에 대해 포스팅 하도록 하겠습니다. :)
코드에 문제가 있다면 댓글로 알려주세요 !
'STUDY > C++' 카테고리의 다른 글
사용자 정의 타입변환과 explicit (0) | 2020.06.01 |
---|---|
스마트포인터 shared_ptr 구현하기 (2) - thread safety (0) | 2020.05.30 |
편리하지만 주의해야 하는 연산자 오버로딩 operator (0) | 2020.04.22 |
std::map에 관한 고찰 (0) | 2020.04.16 |
선언과 정의에 따른 메모리 (0) | 2019.06.23 |