[boost] weak_ptr DarkKaiser, 2015년 3월 20일2023년 9월 6일 출처 : 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; } 5. weak_ptr 정리 weak_ptr은 다음과 같은 경우에 사용하면 유용하다. 어떠한 객체를 참조하되, 객체의 수명에 영향을 주고 싶지 않은 경우 그리고 매번 특정 객체의 ID로 컬렉션에서 검색하고 싶지 않을 경우 그러면서 dangling pointer의 잠재 위험성을 없애고 싶을 때 C/C++/VC++ boostweak_ptr