[Effective Modern C++] 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
weak_ptr
weak_ptr는 언제든 소멸될 수 있는 객체를 사용할 때 쓰는 포인터이다.
대체로 weak_ptr은 shared_ptr을 이용해서 생성하지만 reference count를 증가시키지 않는다. 즉, 객체의 소멸에 관여하지 않는다.
대상을 잃은 weak_ptr은 만료되며 만료 여부는 멤버 함수 expired가 돌려주는 값으로 판단할 수 있다.
만료되지 않은 weak_ptr이라고 해도 역참조 연산이 없기 때문에 피지칭 객체에 직접 접근하는 것은 불가능하다.
만일 역참조 연산이 가능하게 하도록 한다고 하면, 사용하려고 하는 순간 이미 객체가 소멸되어 미정의 행동이 나올 수도 있다.
// std::weak_ptr가 가리키는 피지칭 객체를 역참조 하려면 std::weak_ptr로부터 std::shared_ptr를 얻고 그 std::shared_ptr을 역참조한다.
// 방법 1: lock 멤버 함수를 사용한다.
std::shared_ptr<Widget> spw1 = wpw.lock(); // wpw가 만료이면 spw1은 널
auto spw2 = wpw.lock(); // 위와 동일하나 auto를 사용했음
// 방법 2: std::shared_ptr의 생성자에 std::weak_ptr를 넘겨준다.
std::shared_ptr<Widget> spw3(wpw); // wpw가 만료이면 std::bad_weak_ptr(예외)가 발생
효율성 면에서 weak_ptr과 shared_ptr은 동일하며, 크기도 같고 같은 제어블록을 사용한다.
차이점은 weak_ptr은 객체의 소유권 공유에 참여하지 않으며, 따라서 피지칭 객체의 참조 카운트에 영향을 미치지 않고 두 번째 참조 카운트만 조작한다.
weak_ptr 용도
캐싱
데이터 베이스에서 가져온 데이터를 캐싱해놓는다고 가정하면, 성능상의 이슈로 인해 사용하지 않는 객체는 소멸시켜야함을 알 수 있다.
만약 캐싱한 객체에서 reference count를 증가시킨다면 소멸시점에 대해 알기 어려울 것이므로 이 경우 weak_ptr이 적절하다.
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache;
auto objPtr = cache[id].lock();
if (!objPtr)
{
objPtr = loadWidget(id);
cache[id] = objPtr;
}
return objPtr;
}
옵저버 패턴
옵저버 패턴에서 관찰자는 관찰 대상들을 담는 컨테이너를 사용한다. 관찰자들은 관찰 대상의 소멸 여부에는 관심이 있지만 수명에는 관여하지 않는다. 따라서 이 경우도 weak_ptr이 적절하다.
순환참조
A와 B, C 객체가 존재한다고 하고 A와 C가 B객체를 소유하는 상황을 가정해보자.
이 상황 속에서 B가 A를 가리키는 포인터가 필요하게 된 경우 어떤 포인터를 사용해야 할까?
1. raw 포인터
2. shared_ptr
3. weak_ptr
만약 raw포인터를 사용한다면 A가 소멸됐을 때 B가 여부를 알 수 없어 null ptr에 접근하게 된다.
그리고 shared_ptr은 A가 B를 소유하고 B가 A를 소유하게 되는 상황으로 순환참조를 하게 되어 서로 영원히 소멸되지 않는 문제가 생긴다.
weak_ptr은 널포인터에 접근하지도 않고 순환참조가 발생하지도 않아 가장 좋은 선택이 될 수 있다.
'Books > Effective Modern C++' 카테고리의 다른 글
댓글
이 글 공유하기
다른 글
-
[Effective Modern C++] 22. Pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라
[Effective Modern C++] 22. Pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라
2022.09.18 -
[Effective Modern C++] 21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라
[Effective Modern C++] 21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라
2022.09.18 -
[Effective Modern C++] 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
[Effective Modern C++] 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
2022.09.18 -
[Effective Modern C++] 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
[Effective Modern C++] 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라
2022.09.18