[boost] weak_ptr

출처 : http://sweeper.egloos.com/3059940

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 메쏘드를 사용해야 한다.
shared_ptr<_Ty> lock() const
{      
    // convert to shared_ptr
    return (shared_ptr<_Elem>(*this, false));
}

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

bool expired() const
{      
    // return true if resource no longer exists
    return (this->_Expired());
}
 
3. 예제
지금까지의 내용에 대한 이해를 돕기 위해 wikipedia에서 소개하는 예제부터 살펴보자.
#include <memory>    //for shared_ptr/weak_ptr
#include <iostream>
 
using namespace std;
 
int main(int argc, char** argv)
{
    // strong refCount = 1
    shared_ptr<int> sp1(new int(5));
 
    // shared_ptr sp1으로부터 복사 생성
    // weak_ptr이 참조하여, strong refCount = 1, weak refCount = 1
    weak_ptr<int> wp1 = sp1;
    {
        // wp1이 참조하고 있던 sp1을 weak_ptr::lock 메써드를 이용해 sp2가 참조
        // string refCount = 2, weak refCount = 1
        shared_ptr<int> sp2 = wp1.lock();
        if (sp2)
        {
            // weak_ptr<_Ty>의 _Ty 포인터에 엑세스 하려면
            // 이렇게 shared_ptr로 convert하는 방법 밖에 없다
        }
        // sp2가 여기에서 소멸, strong RefCount = 1, weak refCount = 1
    }
 
    // sp1.reset으로 인해 strong refCount = 0, 즉 sp1 소멸
    // wp1이 참조하고 있던 sp1이 소멸되었으므로, wp1은 expired
    sp1.reset();
 
    // expired된 wp1은 참조하고 있는 shared_ptr이 없다.
    // 따라서, sp3도 empty
    shared_ptr<int> sp3 = wp1.lock();
    if (sp3)
    {
        // 여기 문장은 실행되지 않는다
    }
 
    return 0;
}
4. Circular reference 회피 예제
shared_ptr 문서의 circular reference 예제를 weak_ptr을 사용해 개선시켜 보았다.
아래 예제와 비교해 보길 바란다.
#include <memory>    // for shared_ptr
#include <vector>
 
using namespace std;
 
class User;
typedef shared_ptr<User> UserPtr;
 
class Party
{
public:
    Party() {}
    ~Party() { m_MemberList.clear(); }
 
public:
    void AddMember(const UserPtr& member)
    {
        m_MemberList.push_back(member);
    }
 
    void RemoveMember()
    {
        // 제거 코드
    }
 
private:
    typedef vector<UserPtr> MemberList;
    MemberList m_MemberList;
};
typedef shared_ptr<Party> PartyPtr;
typedef weak_ptr<Party> PartyWeakPtr;
 
class User
{
public:
    void SetParty(const PartyPtr& party)
    {
        m_Party = party;
    }
 
    void LeaveParty()
    {
        if (m_Party)
        {
            // shared_ptr로 convert 한 뒤, 파티에서 제거
            // 만약, Party 클래스의 RemoveMember가 이 User에 대해 먼저 수행되었으면,
            // m_Party는 expired 상태
            PartyPtr partyPtr = m_Party.lock();
            if (partyPtr)
            {
                partyPtr->RemoveMember();
            }
        } 
    }
 
private:
    // PartyPtr m_Party;
    PartyWeakPtr m_Party;    // weak_ptr을 사용함으로써, 상호 참조 회피
};
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    // strong refCount = 1;
    PartyPtr party(new Party);
 
    for (int i = 0; i < 5; i++)
    {
        // 이 UserPtr user는 이 스코프 안에서 소멸되지만,
        // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
        UserPtr user(new User);
         
        party->AddMember(user);
     
        // weak_ptr로 참조하기에 party의 strong refCount = 1
        user->SetParty(party);
    }
    // for 루프 이후 strong refCount = 1, weak refCount = 5
 
    // 여기에서 party.reset을 수행하면, strong refCount = 0
    // 즉, 파티가 소멸되고 그 과정에서 m_MemberList가 clear -> user들의 strong RefCount = 0 -> user 소멸
    // party와 5개의 user 모두 정상적으로 소멸
    party.reset();
 
    return 0;
}
4. weak_ptr 정리
weak_ptr은 다음과 같은 경우에 사용하면 유용하다.
  • 어떠한 객체를 참조하되, 객체의 수명에 영향을 주고 싶지 않은 경우
  • 그리고 매번 특정 객체의 ID로 컬렉션에서 검색하고 싶지 않을 경우
  • 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다