광고


[TR1] weak_ptr TR1/C++11/C++14/C++17




1. shared_ptr

shared_ptr의 내용은 다음 링크를 참고하기 바라며, 특히 3-9 Circular reference 챕터를 자세히 읽어보기 바란다.
(위 링크엔 shared_ptr의 circular reference에 대한 예제가 포함되어 있다)


2. weak_ptr

shared_ptr은 자신이 참조하고 있는 객체(메모리 주소)에 대해 reference counting을 함으로써, 객체의 수명에 직접적으로 관여한다.
shared_ptr 객체 하나가 소멸되더라도, 동일한 메모리 주소를 참조하고 있는 다른 shared_ptr 객체가 있으면 참조하고 있던 메모리 주소의 객체는 소멸되지 않는다.

하지만, weak_ptr은 shared_ptr을 관리하기 위한 reference count에 포함되지 않는다.
즉, shared_ptr의 객체만 참조할 뿐, shared_ptr의 reference count를 올리지 않는 것이다.

사실 weak_ptr이 shared_ptr을 참조할 때 shared_ptr의 weak reference count는 증가시킨다.
객체의 생명 주기에 관여하는 strong reference count를 올리지 않는 것 뿐이다.
(shared_ptr, weak_ptr 객체를 디버거로 살펴보면 strong/weak refCount가 따로 표시된다)
(weak reference count는 객체의 소멸에는 전혀 관여하지 않으니 헤깔리지 말도록!)

위에서 얘기한 것처럼, weak_ptr은 shared_ptr의 참조자라고 표현하는 것이 맞을 듯 하다.

같은 weak_ptr 또는 shared_ptr로부터만 복사 생성/대입 연산이 가능하며,
shared_ptr로만 convert가 가능하다.

따라서, weak_ptr<_Ty>는 _Ty 포인터에 대해 직접 access가 불가능하며, 
(shared_ptr의 get() 메쏘드 같은 녀석이 아예 없다)
_Ty 포인터에 엑세스를 원하면 lock 메써드를 통해 shared_ptr로 convert 한 뒤, shared_ptr의 get 메쏘드를 사용해야 한다.

  1. shared_ptr<_Ty> lock() const
  2. {      
  3.     // convert to shared_ptr
  4.     return (shared_ptr<_Elem>(*thisfalse));
  5. }

그리고  expired 함수를 통해 자신이 참조하고 있는 shared_ptr의 상태(즉, weak_ptr의 상태)를 체크할 수 있다.

  1. bool expired() const
  2. {      
  3.     // return true if resource no longer exists
  4.     return (this->_Expired());
  5. }


3. 예제

지금까지의 내용에 대한 이해를 돕기 위해 wikipedia에서 소개하는 예제부터 살펴보자.

  1. #include <memory>    //for shared_ptr/weak_ptr
  2. #include <iostream>
  3.  
  4. using namespace std;
  5.  
  6. int main(int argc, char** argv)
  7. {
  8.     // strong refCount = 1
  9.     shared_ptr<int> sp1(new int(5));
  10.  
  11.     // shared_ptr sp1으로부터 복사 생성
  12.     // weak_ptr이 참조하여, strong refCount = 1, weak refCount = 1
  13.     weak_ptr<int> wp1 = sp1;
  14.     {
  15.         // wp1이 참조하고 있던 sp1을 weak_ptr::lock 메써드를 이용해 sp2가 참조
  16.         // string refCount = 2, weak refCount = 1
  17.         shared_ptr<int> sp2 = wp1.lock();
  18.         if (sp2)
  19.         {
  20.             // weak_ptr<_Ty>의 _Ty 포인터에 엑세스 하려면
  21.             // 이렇게 shared_ptr로 convert하는 방법 밖에 없다
  22.         }
  23.         // sp2가 여기에서 소멸, strong RefCount = 1, weak refCount = 1
  24.     }
  25.  
  26.     // sp1.reset으로 인해 strong refCount = 0, 즉 sp1 소멸
  27.     // wp1이 참조하고 있던 sp1이 소멸되었으므로, wp1은 expired
  28.     sp1.reset();
  29.  
  30.     // expired된 wp1은 참조하고 있는 shared_ptr이 없다.
  31.     // 따라서, sp3도 empty
  32.     shared_ptr<int> sp3 = wp1.lock();
  33.     if (sp3)
  34.     {
  35.         // 여기 문장은 실행되지 않는다
  36.     }
  37.  
  38.     return 0;
  39. }


4. Circular reference 회피 예제

shared_ptr 문서의 circular reference 예제를 weak_ptr을 사용해 개선시켜 보았다.
아래 예제와 비교해 보길 바란다.

  1. #include <memory>    // for shared_ptr
  2. #include <vector>
  3.  
  4. using namespace std;
  5.  
  6. class User;
  7. typedef shared_ptr<User> UserPtr;
  8.  
  9. class Party
  10. {
  11. public:
  12.     Party() {}
  13.     ~Party() { m_MemberList.clear(); }
  14.  
  15. public:
  16.     void AddMember(const UserPtr& member)
  17.     {
  18.         m_MemberList.push_back(member);
  19.     }
  20.  
  21.     void RemoveMember()
  22.     {
  23.         // 제거 코드
  24.     }
  25.  
  26. private:
  27.     typedef vector<UserPtr> MemberList;
  28.     MemberList m_MemberList;
  29. };
  30. typedef shared_ptr<Party> PartyPtr;
  31. typedef weak_ptr<Party> PartyWeakPtr;
  32.  
  33. class User
  34. {
  35. public:
  36.     void SetParty(const PartyPtr& party)
  37.     {
  38.         m_Party = party;
  39.     }
  40.  
  41.     void LeaveParty()
  42.     {
  43.         if (m_Party)
  44.         {
  45.             // shared_ptr로 convert 한 뒤, 파티에서 제거
  46.             // 만약, Party 클래스의 RemoveMember가 이 User에 대해 먼저 수행되었으면,
  47.             // m_Party는 expired 상태
  48.             PartyPtr partyPtr = m_Party.lock();
  49.             if (partyPtr)
  50.             {
  51.                 partyPtr->RemoveMember();
  52.             }
  53.         }  
  54.     }
  55.  
  56. private:
  57.     // PartyPtr m_Party;
  58.     PartyWeakPtr m_Party;    // weak_ptr을 사용함으로써, 상호 참조 회피
  59. };
  60.  
  61.  
  62. int _tmain(int argc, _TCHAR* argv[])
  63. {
  64.     // strong refCount = 1;
  65.     PartyPtr party(new Party);
  66.  
  67.     for (int i = 0; i < 5; i++)
  68.     {
  69.         // 이 UserPtr user는 이 스코프 안에서 소멸되지만,
  70.         // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
  71.         UserPtr user(new User);
  72.          
  73.         party->AddMember(user);
  74.      
  75.         // weak_ptr로 참조하기에 party의 strong refCount = 1
  76.         user->SetParty(party);
  77.     }
  78.     // for 루프 이후 strong refCount = 1, weak refCount = 5
  79.  
  80.     // 여기에서 party.reset을 수행하면, strong refCount = 0
  81.     // 즉, 파티가 소멸되고 그 과정에서 m_MemberList가 clear -> user들의 strong RefCount = 0 -> user 소멸
  82.     // party와 5개의 user 모두 정상적으로 소멸
  83.     party.reset();
  84.  
  85.     return 0;
  86. }


4. weak_ptr 정리

weak_ptr은 다음과 같은 경우에 사용하면 유용하다.
  • 어떠한 객체를 참조하되, 객체의 수명에 영향을 주고 싶지 않은 경우
  • 그리고 매번 특정 객체의 ID로 컬렉션에서 검색하고 싶지 않을 경우
  • 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때


핑백

  • 수까락의 프로그래밍 이야기 : [TR1] shared_ptr 2013-11-12 23:41:43 #

    ... 고 있으면, 들고 있던 포인터가 dangling pointer가 될 수 있는 위험이 있다. 이럴 때, User 객체가 Party 객체를 shared_ptr가 아닌 weak_ptr을 사용하여 들고 있다면, 검색 비용 회피와 dangling pointer의 위험에서 모두 벗어날 수 있다. std::weak_ptr은 shared_ptr로 ... more

  • [boost] weak_ptr &#8211; DarkKaiser의 블로그 2017-09-13 10:53:01 #

    ... 검색하고 싶지 않을 경우 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때 출처:[TR1] weak_ptr boost weak_ptr 답글 남기기 응답 취소 이메일은 공개되지 않습니다. 필수 입력창은 ... more

  • [boost] shared_ptr &#8211; DarkKaiser의 블로그 2017-09-13 10:59:00 #

    ... 럴 때, User 객체가 Party 객체를 shared_ptr가 아닌 weak_ptr을 사용하여 들고 있다면, 검색 비용 회피와 dangling p ... more

덧글

  • Shae 2015/09/15 15:40 # 답글

    아주 좋은 글 잘 읽었습니다 (__)
댓글 입력 영역