현대적인 C++에서는 직접적인 포인터를 사용하지 않고 스마트 포인터를 이용하여 간접적으로 활용하는게 일반적이다.

언리얼 엔진만 보더라도 전부 스마트 포인터로 되어있다.

 

스마트 포인터 : 포인터를 알맞는 정책에 따라 관리하는 객체 (포인터를 래핑해서 사용)

 

shared_ptr

레퍼런스 카운트를 통해 레퍼런스 카운트가 0일때만 해제된다.

명시적으로 delete를 해줄 필요 없이 특정 조건(레퍼런스 카운트 == 0)을 만족하면 알아서 delete를 해준다.

make_shared<>()를 통해 초기화 시켜주는게 성능면에서 조금 이점이 있다고 한다. (메모리 블럭을 한번에 만들어준다는 차이가 있음)

간단하게 구현한 코드

더보기
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--;
			cout << "RefCount : " << _block->_refCount << endl;
		}

		if (_block->_refCount == 0)
		{
			delete _ptr;
			delete _block;
			cout << "Delete Data" << endl;
		}
	}

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

 

사이클이 발생하는 경우 소멸이 되지 않는 문제가 있다.

사이클이 발생하지 않는지 잘 확인해서 사이클을 끊어주어야 한다.

 

 

weak_ptr

ref count(객체를 참고하는게 몇개인지)와 더불어 weak count(weak_ptr이 몇개가 참조 중인지)가 같이 관리된다.

shared_ptr과 다르게 객체 삭제 시점에 ref count block은 삭제하지 않고 냅둔다.

또한 shared_ptr처럼 바로 사용할수는 없고 expired()로 삭제 여부를 검사 후 lock()을 통해 shared_ptr를 반환 받아 사용해야 한다.

weak_ptr<Knight> _target;
if (_target) // 오류! shared_ptr처럼 사용 불가능
{
	// ..
}

// ------------

weak_ptr<Knight> _target;
if (_target.expired() == false)
{
	shared_ptr<Knight> sptr = _target.lock();
    // ..
}

 

장점은 생명 주기에서 자유로워진다. weak_ptr 자체는 객체의 소멸 자체에는 영향을 주지 않기때문에 순환 구조가 일어날 수 없다.

단점은 expired()로 명시적으로 체크 후 shared_ptr로 다시 한번 전환하는 과정이 추가되기 때문에 번거로워진다.

상황에 맞춰서 shared_ptr만 사용할 것인지, weak_ptr도 병행해서 사용할 것인지 잘 고르면 된다.

 

 

unique_ptr

단 하나만 존재할 수 있다.

다른 곳에 넘겨줄때는 move를 통해 모두 이양해서 사용해야 한다.

일반적인 복사가 막히고 이동만 가능한 포인터라고 이해하면 된다.

make_unique<>()를 통해 초기화 하면 된다.

 

쌩으로 포인터를 사용하는 것은 더 이상 없다고 생각하면 된다.

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

[Modern C++] 람다 (lambda)  (0) 2022.08.31
[Modern C++] 전달 참조(forwarding reference)  (0) 2022.08.31
[Modern C++] 오른값 참조 (rvalue reference)  (0) 2022.08.31
[Modern C++] #2  (0) 2022.08.31
[Modern C++] #1  (0) 2022.08.31

+ Recent posts