Circular references


http://sweeper.egloos.com/viewer/2826435

http://goo.gl/B1mEP9\


 

9) Circular references

레퍼런스 카운팅 기반이기에 순환 참조에 대한 잠재적인 문제가 있을 수 있다.
즉, A와 B가 서로에 대한 shared_ptr을 들고 있으면 레퍼런스 카운트가 0이 되지 않아 메모리가 해제되지 않는다.

게임의 경우를 예로 들어, 파티 객체가 있으면 파티원 객체가 있을 것이다.
파티 객체는 파티원 객체를 리스트 형태로 들고 있고, 또 파티원도 자기가 속한 파티 객체를 알기 위해 참조 형태로 들고 있는 예제를 살펴보자.

  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. private:
  22.     typedef vector<UserPtr> MemberList;
  23.     MemberList m_MemberList;
  24. };
  25. typedef shared_ptr<Party> PartyPtr;
  26.  
  27. class User
  28. {
  29. public:
  30.     void SetParty(const PartyPtr& party)
  31.     {
  32.         m_Party = party;
  33.     }
  34.  
  35. private:
  36.     PartyPtr m_Party;
  37. };
  38.  
  39.  
  40. int _tmain(int argc, _TCHAR* argv[])
  41. {
  42.     PartyPtr party(new Party);
  43.  
  44.     for (int i = 0; i < 5; i++)
  45.     {
  46.         // 이 user는 이 스코프 안에서 소멸되지만,
  47.         // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
  48.         UserPtr user(new User);
  49.  
  50.         // 아래 과정에서 순환 참조가 발생한다.
  51.         party->AddMember(user);
  52.         user->SetParty(party);
  53.     }
  54.  
  55.     // 여기에서 party.reset을 수행해 보지만,
  56.     // 5명의 파티원이 party 객체를 물고 있어 아직 refCount = 5 의 상태
  57.     // 따라서, party가 소멸되지 못하고, party의 vector에 저장된 user 객체들 역시 소멸되지 못한다.
  58.     party.reset();
  59.  
  60.     return 0;
  61. }

위와 같은 형태로 shared_ptr이 서로를 참조하고 있는 것은 circular reference라고 한다.

위 예제처럼, 그룹 객체 - 소속 객체간 상호 참조는 실무에서도 흔히 볼 수 있는 패턴이며, 
보통은 위 예제처럼 직접 참조 형식이 아니라, User는 PartyID를 들고 있고, Party 객체에 접근 필요시 PartyManger(컬렉션)에 질의해서 유효한 Party 객체 포인터를 얻어오는 방식을 사용한다.

그렇다고, PartyManager에 일일히 ID로 검색하는 비용을 줄이고자, Party 포인터를 직접 들고 있으면, 들고 있던 포인터가 dangling pointer가 될 수 있는 위험이 있다.

이럴 때, User 객체가 Party 객체를 shared_ptr가 아닌 weak_ptr을 사용하여 들고 있다면, 검색 비용 회피와 dangling pointer의 위험에서 모두 벗어날 수 있다.

std::weak_ptr은 shared_ptr로부터 생성될 수 있고, shared_ptr이 가리키고 있는 객체를 똑같이 참조하지만, 참조만 할 뿐 reference counting은 하지 않아 위 예제의 목적에 가장 바람직한 대안이 될 수 있다 할 수 있다.






반응형

+ Recent posts