C++ Circular References: shared_ptr Leaks and Breaking
이 글의 핵심
Why shared_ptr cycles leak memory, how weak_ptr breaks cycles, parent/child and cache/observer patterns, use_count debugging, Valgrind, and ASan LeakSanitizer.
Ownership tradeoffs: [shared_ptr vs unique_ptr](/en/blog/cpp-comparison-04-shared-unique-ptr/ · [smart pointers](/en/blog/cpp-smart-pointers/ · [leak detection](/en/blog/cpp-error-07-memory-leak-detection/.
Introduction: “I used shared_ptr but I still leak”
“The reference count never hits zero”
std::shared_ptr manages lifetime automatically, but cycles of shared_ptr keep strong counts ≥ 1, so destructors never run.
// 타입 정의
struct Node {
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // cycle: use weak_ptr for one direction
};
This article covers:
- What cycles are and how refcounting interacts
weak_ptrfundamentals- Patterns: parent/child, caches, observers
- Debugging leaks and suspicious counts
Table of contents
1. Circular references
Reference counting refresher
Copying shared_ptr increases the strong count; reset/destruction decreases it. At 0, the managed object is destroyed.
Cycle example (conceptual)
Two Person objects point to each other with shared_ptr. After function scope ends, each object is still reachable from the other → neither destructor runs.
2. weak_ptr
std::weak_ptr observes the same control block but does not increase the strong count.
auto p = std::make_shared<int>(42);
std::weak_ptr<int> w = p;
p.reset();
// w may be expired; use lock() before access
if (auto s = w.lock()) {
std::cout << *s << '\n';
}
Breaking cycles
Make one direction of a mutual association a weak_ptr (typically the “back-pointer” or observer side).
3. Practical patterns
Parent / child
- Own children with shared_ptr (or unique ownership at leaves—design-dependent).
- Child→parent as weak_ptr when parent must not be kept alive solely by children. Trees often combine enable_shared_from_this to pass shared_from_this() when registering a child.
Cache with weak values
Store weak_ptr
Observer lists
Observers as weak_ptr so subjects do not keep observers alive forever; prune expired() entries periodically.
4. Debugging
- Log use_count() when diagnosing surprising lifetimes.
~T()logging: if never called, suspect cycles or forgotten releases.- LeakSanitizer / Valgrind for heap leak reports.
Common mistakes (short)
- Doubly-linked list with
shared_ptrboth ways → make prev a weak_ptr. - Publisher/listener mutual
shared_ptr→ listener holds weak_ptr. - Child holds
shared_ptr<Parent>while parent holds shared_ptr→ switch child to weak_ptr unless you truly co-own.
Troubleshooting
Symptom: memory grows unbounded
Check use_count(), review mutual shared_ptr fields, run LSan/Valgrind.
Symptom: crash on shutdown
Using weak_ptr after destruction—always lock() and test for nullptr.
Performance note
weak_ptr::lock() involves atomic operations; usually fine compared to correctness. Prefer clear ownership DAGs over ad-hoc cycles.
Summary
Relationship cheat sheet
| Relation | Strong | Weak |
|---|---|---|
| Parent→child (owning) | shared_ptr / unique_ptr | — |
| Child→parent (non-owning) | — | weak_ptr |
| Cache value | — | weak_ptr |
| Observer | — | weak_ptr |
Rules
- Break cycles with weak_ptr on the non-owning edge.
- lock() before use; handle expiration.
- enable_shared_from_this when a member must hand out
shared_ptrto*this.
Checklist
- Any mutual shared_ptr pairs?
- Back-edges use weak_ptr?
- lock() results checked?
- Destructor logs confirm teardown in tests?
Related posts (internal)
Keywords
circular reference, shared_ptr leak, weak_ptr, reference counting, LeakSanitizer
Practical tips
- Review any
shared_ptrfield pointing “up” or sideways in graphs. - Prefer weak_ptr for observers and caches.
- Add CI tests that construct/destroy graphs and assert destructor side effects (where safe).
Closing
Circular shared_ptr graphs leak because strong counts never reach zero. Model ownership as a DAG, use weak_ptr for back-edges and observers, and validate with sanitizers and use_count() probes during development.
Next: Deepen with the full smart pointers article and memory leak tooling.
More related posts
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Why shared_ptr cycles leak memory, how weak_ptr breaks cycles, parent/child and cache/observer patterns, use_count debug… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ shared_ptr vs unique_ptr: Smart Pointer Choice Complete](/en/blog/cpp-comparison-04-shared-unique-ptr/
- [Finding C++ Memory Leaks: Valgrind, AddressSanitizer, and](/en/blog/cpp-error-07-memory-leak-detection/
- [C++ Smart Pointers: unique_ptr, shared_ptr & Memory-Safe](/en/blog/cpp-smart-pointers/
이 글에서 다루는 키워드 (관련 검색어)
C++, Circular reference, shared_ptr, weak_ptr, Memory leak, Smart pointers 등으로 검색하시면 이 글이 도움이 됩니다.