* 정의
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 |