3. shared_ptr 제어블록, 순환 참조, weak_ptr
난이도 상내 리뷰 · C++
해설 · C++
해설 — shared_ptr 제어블록, 순환 참조, weak_ptr
난이도: 상
답변 프레임워크: 요약 → 핵심 개념 → 단계별 내부 동작 → 흔한 오해·함정 → 면접 포인트
요약
예상 출력:
ctor A
a.use_count=1
after weak, a.use_count=1
in block, a.use_count=2
after block, a.use_count=1
ctor X
ctor Y
x.use_count=2, y.use_count=2
x.use_count after shared_from_this=3
end of main
dtor A
핵심: dtor X 와 dtor Y 는 출력되지 않는다(누수). x 와 y 가 서로를 next(강한
참조)로 가리켜 참조계수가 서로 1씩 받쳐주기 때문이다. weak_ptr w 는 강한 계수를 올리지
않아 a.use_count 가 1 그대로다. shared_from_this() 는 같은 제어블록을 공유하는 새
shared_ptr 를 만들어 x 의 강한 계수를 2→3 으로 올린다.
핵심 개념
- 제어블록(control block):
shared_ptr가 가리키는 객체와 별도로 존재하는 메타데이터. ① strong count(use_count): 객체 수명 관리, ② weak count: 제어블록 수명 관리. strong→0 이면 객체 파괴, strong=0 이고 weak=0 이면 제어블록 해제. - make_shared: 객체 + 제어블록을 한 번의 할당으로 묶는다(메모리 1개, 지역성↑).
shared_ptr<T>(new T)는 두 번 할당. - 원자적 참조계수: strong/weak 증감은 원자적(스레드 안전). 단 "가리키는 객체의 내용"은 보호하지 않는다(데이터 레이스는 별개). 또 atomic 증감 비용이 있으니 핫패스에서는 불필요한 복사(계수 증감)를 줄여야 한다.
- weak_ptr: 강한 계수를 올리지 않는 관찰자.
lock()으로 살아있으면 shared_ptr 승격, 죽었으면 빈 shared_ptr. 순환을 끊는 도구. - enable_shared_from_this: 객체가 자신을 소유하는 제어블록을 알고 있어
shared_from_this()로 같은 제어블록의 shared_ptr 를 만든다(주의: 반드시 이미 shared_ptr 로 소유된 상태여야).
단계별 내부 동작
make_shared<Node>("A")→ 객체+제어블록 한 번에.a.use_count=1.weak_ptr w = a→ weak count 만 증가, strong 그대로 1.- 블록 내
b = a→ strong 2. 블록 종료로b소멸 → strong 1. x,y생성(각 strong 1).x->next = y(y strong 2),y->next = x(x strong 2). 출력x.use_count=2, y.use_count=2.self = x->shared_from_this()→ x 제어블록 공유, x strong 3.self.reset()→ x strong 2.- main 종료, 지역 역순 소멸(self→y→x→w→a):
- 지역
y소멸 → y strong 2→1(아직x->next가 가리킴). 파괴 안 됨. - 지역
x소멸 → x strong 2→1(아직y->next가 가리킴). 파괴 안 됨. - 이제 X·Y 는 서로의
next로만 1씩 받쳐진다 → strong 0 이 되지 못해 영구 누수. w(weak) 소멸: 영향 없음.- 지역
a소멸 → strong 1→0 → 객체 파괴 →dtor A(마지막 출력).
- 지역
흔한 오해·함정
- "shared_ptr 면 누수가 없다"는 오해: 순환 참조는 누수를 만든다. 부모→자식은 shared, 자식→부모(역참조)는 weak 로 끊는다(트리/그래프, 옵저버, 세션↔룸 양방향 등).
shared_from_this()를 생성자에서 호출: 아직 제어블록이 없어 UB/예외. 객체가 shared_ptr 로 소유된 뒤에만 호출.- 계수 원자성 ≠ 데이터 안전: 여러 스레드가 같은 객체 내용을 동시 수정하면 별도 동기화 필요. 또 "같은 shared_ptr 인스턴스"를 여러 스레드가 동시에 재대입하는 것도 레이스.
- 성능: shared_ptr 복사마다 원자 증감. 핫패스는
const shared_ptr&/T*(소유 안 함)로 전달해 불필요한 계수 변동을 피한다. 소유가 단일하면unique_ptr.
면접 포인트
- 제어블록의 strong/weak count 역할과 객체·제어블록 수명 분리.
make_shared의 단일 할당. - 순환 참조 누수의 원인과 weak_ptr 로 끊는 패턴(세션↔룸, 부모↔자식).
lock()의미. - 참조계수는 원자적이지만 객체 내용은 아니다 — 동시성에서 무엇이 보장되고 안 되는지.
핫패스 복사 비용과
unique_ptr/참조 전달.