일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- operator new
- 티스토리챌린지
- Effective c++
- universal reference
- 언리얼
- 상속
- reference
- more effective c++
- implicit conversion
- 게임
- 반복자
- UE4
- resource management class
- effective modern c++
- 오블완
- 예외
- 영화 리뷰
- c++
- 암시적 변환
- virtual function
- 스마트 포인터
- 참조자
- iterator
- effective stl
- std::async
- Smart Pointer
- lua
- 영화
- exception
- 보편 참조
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 20. std::weak_ptr 본문
728x90
항목 20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라
std::weak_ptr
- 소유권 공유에는 참여하지 않는 스마트 포인터.
- 즉, 관리 대상을 잃은 상황에 대한 대응이 필요하다.
- std::shared_ptr을 보강하는 스마트 포인터이므로 API만으로는 그다지 스마트해 보이지 않는다.
auto spw = std::make_shared<Widget>(); // 레퍼런스 카운트 1
...
std::weak_ptr<Widget> wpw(spw); // 레퍼런스 카운트 1(변화 없음)
...
spw = nullptr; // 레퍼런스 카운트 0, wpw는 관리 대상을 잃었다.
if(wpw.expired()) ... // 관리 대상을 잃었다면
- weak_ptr은 역참조 연산이 존재하지 않는다.(operator->)
- 그 대신 expired 체크 후 shared_ptr을 생성하여 리턴을 아토믹 연산으로 제공해준다.
- 그것이 lock 함수이다.
std::shared_ptr<Widget> spw1 = wpw.lock(); // wpw가 만료되었으면 spw1는 null
- 혹은 weak_ptr을 받는 shared_ptr의 생성자를 사용할 수도 있다.
std::shared_ptr<Widget> spw(wpw); // wpw가 만료라면 std::bad_weak_ptr 예외 발생
std::weak_ptr가 유용한 예제1
std::unique_ptr<const Widget> loadWidget(WidgetID id); // unique_ptr을 리턴하는 팩토리 함수
- 가정 1. loadWidget의 비용이 크다.
- 가정 2. ID들이 되풀이해서 쓰이는 경우가 많다.
- 그렇다면 리턴값들을 캐싱하여 재활용할 수 있을 것이다.
- 재활용하려면 unique_ptr은 좋은 선택이 아니다.
- 리턴 받은 측에서 사용하고 난 후에 자동 파괴될 것이기 때문이다.
- std::shared_ptr로 변경하여 캐싱하도록 재구성.
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
// weak_ptr를 캐싱
static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache;
auto objPtr = cache[id].lock();
if(!objPtr) // 캐시에 없거나 만료되었다면 적재
{
objPtr = loadWidget(id);
cache[i] = opbjPtr;
}
return objPtr;
}
std::weak_ptr가 유용한 예제2
- 관찰자(Observer) 설계 패턴
- 관찰자 패턴에서는 관찰 대상 객체 포인터들을 관리해야 한다.
- 허나, 관찰자 자체가 해당 대상 객체들의 레퍼런스 카운트를 올릴 필요는 없다.
- weak_ptr를 사용하여 해결한다.
std::weak_ptr가 유용한 예제3
- A와 C가 B의 소유권을 공유하고 있다.
- B에서 A를 가리키는 포인터가 필요하게 되었다고 하면, 그 포인터는 무슨 종류여야 할까?
- raw 포인터
- C -> B 상황에서 A가 파괴되면, B가 가진 A에 대한 포인터는 대상을 잃게 된다.
- 허나 B는 그 사실을 알지 못하므로 댕글링 포인터에 접근할 위험이 있다.
- std::shared_ptr
- A와 B는 서로를 가리킨다.
- 순환 참조가 되어 결과적으로 A, B 둘다 절대 파괴되지 못한다.
- 사실상 메모리 누수다.
- std::weak_ptr
- 위의 두 문제가 모두 해결되는 방법이다.
추가적인 내용
- weak_ptr은 shared_ptr과 효율성은 동일하다.
- 객체 크기가 동일하다.
- 제어 블록을 사용한다.
- 생성, 파괴, 할당 연산에 아토믹 레퍼런스 카운트 조작이 관여한다.
- 어라? 레퍼런스 카운트에는 관여하지 않는댔지 않나?
- 사실은 소유권 공유에 참여하지 않으므로, 관리 객체 레퍼런스 카운트에만 영향을 미치지 않을 뿐이다.
- 제어 블록에는 다른 두 번째 레퍼런스 카운트가 있고 거기에는 관여한다.(항목 21 참조)
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 22. unique_ptr를 사용한 pImpl 관용구의 특수 멤버 함수 (0) | 2025.03.07 |
---|---|
[Effective Modern C++] 21. std::make_unique, std::make_shared (0) | 2025.03.06 |
[Effective Modern C++] 19. std::shared_ptr (0) | 2025.03.04 |
[Effective Modern C++] 18. std::unique_ptr (0) | 2025.02.28 |
[Effective Modern C++] 17. 특수 멤버 함수(special member function) (1) | 2025.02.27 |
Comments