* 정의

 

std::shared_ptr와 비슷하게 동작하면서 객체의 참조 횟수에는 영향을 미치지 않는 포인터가 바람직한 경우도 있다. 이러한 스마트 포인터는 자신이 가리키는 객체가 이미 파괴된 상황에서 이를 인지하고 적절하게 대처할 수 있어야 한다. 이를 약한 포인터라고 하며 std::weak_ptr 형식으로 지원하고 있다. 

 

 

* 동작 방식

 

- 특징

 

기본적인 특징은 다음과 같이 정리할 수 있다.

  • 역참조를 지원하지 않으며 널인지 판정할 수 없다.
  • 가리키는 객체를 잃은 std::weak_ptr은 만료 여부를 expired 함수를 통해 판정할 수 있다.
  • 직접 사용하기 위해서는 std::weak_ptr의 lock함수를 사용하여 std::shared_ptr을 만들고 이를 역참조하여 사용해야 한다. 그 이유는 std::weak_ptr에는 역참조 연산이 없으며 만약 있다고 해도 expired로 검사하고 역참조 하는 도중에 객체가 파괴될 수 있기 때문이다. (멀티 스레드 환경)
  • 또한 이 방식을 통해 객체를 사용하는 경우 사용하는 동안 std::shared_ptr에 의해 객체가 파괴되지 않고 보호된다.

 

 

- 사용 예시 - 캐싱

 

특정 데이터를 로드하기 위한 비용이 크고(LoadData 함수의 비용이 크다.) 특정 데이터를 재사용하는 경우가 많다면 LoadData를 통해 로드된 데이터를 캐싱하는 함수를 작성함으로써 성능을 올릴 수 있다.

 

하지만 캐시에 점점 쌓이다보면 이는 또 성능상의 문제가 된다. 따라서 더 이상 쓰이지 않는 데이터는 캐시에서 삭제하는 것으로 최적화할 수 있다. 여기서 std::weak_ptr을 활용할 수 있다.

class DataKey
{
public:
	DataKey(int id)
		: _id(id) {}

	bool operator==(const DataKey& other) const
	{
		return _id == other._id;
	}

public:
	int _id;
};


class DataKeyHasher
{
public:
	size_t operator()(const DataKey& input) const
	{
		return input._id;
	}
};


class Data
{
public:
	Data(int id)
		: _key(id) {}

	// void init(...) {}
	// ...

public:
	DataKey _key;
	// ...
};


std::shared_ptr<Data> LoadData(const DataKey& key)
{
	// ...
	// ...
	return std::shared_ptr<Data>(/* ... */);
}

std::shared_ptr<Data> GetData(const DataKey& key)
{
	static std::unordered_map<DataKey, std::weak_ptr<Data>, DataKeyHasher> cache;
	std::shared_ptr<Data> data = cache[key].lock();

	if (nullptr == data)
	{
		data = LoadData(key);
		cache[key] = data;
	}

	return data;
}

 

 

- 사용 예시 - 관찰자 패턴

 

관찰자 패턴은 관찰 대상이 관찰자들을 가리키는 포인터들을 유지하면서 상태 변화를 관찰자들에게 통지해주는 방식으로 설계된다. 이때 관찰자들을 std::weak_ptr로 설정하면 해당 관찰자가 유효한지 검증 후 접근할 수 있다.

 

 

- 사용 예시 - std::shared_ptr 순환 방지

 

다음과 같은 소유의 관계를 가진다고 가정해보자.

 

만약 여기서 B에서 A에 대한 포인터가 필요하다면 어떤 포인터로 설정하는 것이 좋을 지 생각해봐야한다.

 

  • 생 포인터 : C가 B를 가리키고 있는 상황에서 A가 삭제된다면 B는 허상 포인터를 가지게 된다.
  • std::shared_ptr : A와 B가 서로를 가리키고 있기 때문에 std::shared_ptr 들의 순환이 생기게 되어 둘 다 파괴되지 못한다. 메모리 누수가 발생한다.
  • std::weak_ptr : A가 파괴되면 B의 포인터가 대상을 잃지만 그 사실을 알 수 있다. B의 A에 대한 포인터는 A의 참조 횟수에 영향을 미치지 않게 되기 때문에 A가 정상적으로 파괴될 수 있다.

 

 

- 컨트롤 블록 삭제 시점

 

컨트롤 블록의 구조는 다음과 같이 되어있다.

참조 횟수(reference count)외에도 약한 횟수(weak count)라는 것도 존재한다. 약한 횟수는 컨트롤 블록을 참조하는 std::weak_ptr의 수를 의미한다. std::weak_ptr은 객체가 만료되었는 지 확인하기 위해 컨트롤 블록에서 참조 횟수가 0인지 확인한다. (0이면 만료) 

 

std::shared_ptr 뿐만 아니라 컨트롤 블록을 참조하는 std::weak_ptr들이 존재하는 한(약한 횟수가 0이 아닌 이상) 컨트롤 블록은 존재하여야 한다. 컨트롤 블록을 담고 있는 메모리는 이를 참조하는 마지막 std::shared_ptr와 std::weak_ptr이 파괴된 후에 해제될 수 있다.

 

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

std::forward  (0) 2022.03.27
std::move  (0) 2022.03.26
스마트 포인터 - std::shared_ptr  (0) 2022.03.22
스마트 포인터 - std::unique_ptr  (0) 2022.03.22
스마트 포인터 개요  (0) 2022.03.21

+ Recent posts