출처 : 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의 잠재 위험성을 없애고 싶을 때