서로 참조하는 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이 해제된다
출처