상세 컨텐츠

본문 제목

스마트 포인터

똑똑한 개발/C++ 게임개발

by 성댕쓰 2021. 9. 10. 23:54

본문

저번 시간에 만든 Custom smartpointer는 다음과 같은 단점이 있다.

1) 이미 만들어진 클래스 대상으로 사용불가.

2) 순환(Cycle) 문제.

 

2) 번 문제 예를 살펴보자.

using KnightRef = TSharedPtr<class Knight>;

class Knight : public RefCountable
{
public:
	Knight()
	{
		cout << "Knight()" << endl;
	}

	~Knight()
	{
		cout << "~Knight()" << endl;
	}

	void SetTarget(KnightRef target)
	{
		_target = target;
	}

	KnightRef _target = nullptr;

};

int main()
{
	// 1) 이미 만들어진 클래스 대상으로 사용 불가
	// 2) 순환(Cycle) 문제

	KnightRef k1(new Knight());
	k1->ReleaseRef();
	KnightRef k2(new Knight());
	k2->ReleaseRef();

	k1->SetTarget(k2);
	k2->SetTarget(k1);

	k1 = nullptr;
	k2 = nullptr;
}

k1과 k2가 서로를 SharedPtr로 갖고 있다. k1을 따라가보면 생성될 때 RefCount가 증가하고 k2멤버변수에 복사될 때 RefCount가 증가한다. k1에 nullptr을 넣을 때 RefCount감소하는데 k2 멤버변수로 있던 k1은 k2가 소멸할 때 Release를 호출하지만, 이미 nullptr로 되어있어 RefCount가 감소하지 않는다. 이렇게 메모리 누수가 일어난다.

 

순환참조를 해결하는 방법으로 weak_ptr을 사용할 수 있다. 그전에 shared_ptr, weak_ptr에 대해 알아보자.

shared_ptr<Knight> spr(new Knight());
...
shared_ptr > _Ptr_base
...
private:
    element_type* _Ptr{nullptr};
    _Ref_count_base* _Rep{nullptr};

shared_ptr은 _Ptr_base를 상속받았는데 멤버변수로 _Ptr과 _Rep가 있다. _Ptr은 shared_ptr이 관리하는 포인터이고 _Rep는 Reference Count block이다.

shared_ptr은 두 가지 정보를 [T*][RefCountBlock] 각 주소에 갖고 있는 것이다.

 

shared_ptr<Knight> ptr = make_shared<Knight>()

template <class _Ty>
class _Ref_count_obj2 : public _Ref_count_base

그러나 make_shared를 사용하면 _Ref_count_obj2를 생성하는데 정보를 위와 다르게 [T*   |   RefCountBlock]으로 하나의 주소에 저장한다.

 

RefCountBlock은 두 개의 정보를 관리한다. 하나는 useCount 다른 하나는 weakCount이다.

shared_ptr관련 ref count가 useCount, weak_ptr관련 ref count가 weakCount이다. useCount가 0이면 관리 포인터를 해제하지만 weakCount는 0이 되어도 관리 메모리를 해제하지 않는다. 따라서 멤버변수로 shared_ptr를 가지고 있어 cycle이 생길때 weak_ptr을 사용하면 메모리 누수를 막을 수 있다.

 

weak_ptr을 사용할 때는 관리 메모리가 해제되었는지 확인하는 코드가 필요하다.

 

// RefCountingBlock은 어떤 정보를 가지고 있음?
// uses(shared), weak(weak)

weak_ptr<Knight> wpr = spr3;

// 관리 포인터가 살아있는지 확인하는 코드 필요
bool expired = wpr.expired();
// or
shared_ptr<Knight> spr4 = wpr.lock();
if (spr4 != nullptr)
{

}

1. 순환참조가 일어나는 과정은?

- 객체가 소멸하면서 멤버변수를 소멸 시키는 구조에서, 객체가 소멸되지 않아서 발생함.

2. 순환참조를 방지하는 방법은?

- weak_ptr 이 use_count를 높이지 않고, weak_count가 0보다 커도 관리 포인터를 해제할 수 있음을 활용.

- 순환참조가 일어날 수 있는 shared_ptr 멤버 변수에 weak_ptr을 사용한다.

3. weak_ptr 사용 단점은?

- weak_ptr 사용하면 다시 shared_ptr을 반환받는 코드가 필요함.

 

참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)

'똑똑한 개발 > C++ 게임개발' 카테고리의 다른 글

Stomp allocator  (0) 2021.09.11
Allocator  (0) 2021.09.11
Reference Counting  (0) 2021.09.08
Deadlock 탐지  (0) 2021.09.06
Read-Writer lock  (0) 2021.09.03

관련글 더보기

댓글 영역