스마트 포인터 - weak_ptr

서로 참조하는 shared_ptr

  • 순환 참조하는 shared_ptr은 사용하지 않는대도 자동으로 메모리 해제 불가
// ex 1)
class A {
  int *data;
  std::shared_ptr<A> other;

 public:
  // ...
  
  void set_other(std::shared_ptr<A> o) { other = o; }
};

int main() {
  std::shared_ptr<A> pa = std::make_shared<A>();
  std::shared_ptr<A> pb = std::make_shared<A>();

  // count = 1
  cout << pa.use_count() << endl;
  cout << pb.use_count() << endl;

  pa->set_other(pb);
  pb->set_other(pa);

  // 각 객체의 other가 가리킴으로써
  // count도 각각 2로 증가
  cout << pa.use_count() << endl;
  cout << pb.use_count() << endl;

  // 정상적으로 소멸자를 호출하지 않음
}

// ex 2)
class Node {
  std::vector<std::shared_ptr<Node>> children;
  /* 어떤 타입이 와야할까? */ parent;

  // Node* -> 메모리를 해제하지 않았을 경우 혹은 예외 발생에서 소멸자 호출이 확실치 않음
  // shared_ptr -> 순환 참조의 문제 발생

 public:
  Node(){};
  void AddChild(std::shared_ptr<Node> node) { children.push_back(node); }
};

weak_ptr

  • 일반 포인터와 shared_ptr 포인터의 중간 성격
  • 스마트 포인터처럼 객체를 안전하게 참조
  • shared_ptr와는 달리 참조 개수를 늘리지 않음
  • weak_ptr가 어떤 객체를 가리킨다 하더라도, 그 객체를 가리키는 shared_ptr가 없을 수 있음 (메모리에서 소멸)
    • 따라서 weak_ptr은 그 자체로 객체를 참조할 수 없고, shared_ptr로 변환해서 사용
    • 가리키는 객체가 소멸되었다면 빈 shared_ptr로 변환
    • 유효한 객체를 가리킨다면 그 객체를 가리키는 shared_ptr로 변환
class A {
  std::string s;
  std::weak_ptr<A> other;

 public:
  A(const std::string& s) : s(s) { std::cout << "자원을 획득함!" << std::endl; }

  ~A() { std::cout << "소멸자 호출!" << std::endl; }

  void set_other(std::weak_ptr<A> o) { other = o; }

  // lock 함수를 통해서 weak_ptr을 shared_ptr로 변환할 수 있다
  // 가리키는 객체가 소멸되었다면 (참조 개수가 0이라면) 빈 shared_ptr로 변환
  // 유효한 객체를 가리킨다면 그 객체를 가리키는 shared_ptr로 변환
  void access_other() {
    std::shared_ptr<A> o = other.lock();
    if (o) {
      std::cout << "접근 : " << o->name() << std::endl;
    } else {
      std::cout << "이미 소멸됨 ㅠ" << std::endl;
    }
  }
  std::string name() { return s; }
};

int main() {
  std::vector<std::shared_ptr<A>> vec;
  vec.push_back(std::make_shared<A>("자원 1"));
  vec.push_back(std::make_shared<A>("자원 2"));

  // weak_ptr은 생성자로 다른 weak_ptr나 shared_ptr을 받는다
  // control block이 없는 일반 포인터 주소값으로 weak_ptr를 생성할 수 없다
  vec[0]->set_other(vec[1]);
  vec[1]->set_other(vec[0]);

  // pa 와 pb 의 ref count 는 그대로다.
  std::cout << "vec[0] ref count : " << vec[0].use_count() << std::endl;
  std::cout << "vec[1] ref count : " << vec[1].use_count() << std::endl;

  // weak_ptr 로 해당 객체 접근하기
  // v[0]에서 v[1]의 other로 접근 시도
  vec[0]->access_other();

  // 벡터 마지막 원소 제거 (vec[1] 소멸)
  vec.pop_back();

  // 원래 v[1]이었던 원소에서 원래 v[0]의 other로 접근 시도
  vec[0]->access_other();  // 접근 실패!

  // 나머지 원소의 소멸자 호출
}
  • lock 함수를 이용해 weak_ptr를 shared_ptr로 변환할 수 있다
    • 이때 empty를 반환한다면 메모리는 이미 해제되었다는 뜻이다
    • 이 과정을 통해 메모리 해제 여부를 확인할 수 있다

weak count

  • shared_ptr의 ref count가 0이 된다하더라도 control block는 해제하지 않는다
    • 이미 ref count가 0이기 때문에 객체는 매모리에서 소멸되었다
    • 하지만 contorl block도 해제하면, ref count가 0이라는 것을 확인할 수 없다
    • 객체를 가리키는 shared_ptr이 0개라 하더라도 weak_ptr이 남아 있을 수 있다
  • control block은 ref count와 weak count 모두를 기록한다
    • control block도 해제하기 위해서는 weak count도 0이어야 한다
    • weak_ptr도 모두 해제된 다음, 맨 마지막으로 control block이 해제된다

출처

Categories: ,

Updated: