본문 바로가기

STUDY/C++

스마트포인터 shared_ptr 구현하기 (1)

오늘부터 여러번에 걸쳐 스마트 포인터 중에서도 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에 대해 포스팅 하도록 하겠습니다. :) 

 

코드에 문제가 있다면 댓글로 알려주세요 !