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 · smart pointers · 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
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Why shared_ptr cycles leak memory, how weak_ptr breaks cycles, parent/child and cache/observer patterns, use_count.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ shared_ptr vs unique_ptr: Smart Pointer Choice Complete
- Finding C++ Memory Leaks: Valgrind, AddressSanitizer, and
- C++ Smart Pointers: unique_ptr, shared_ptr & Memory-Safe
Keywords Covered in This Article (Related Search Terms)
This article covers C++, Circular reference, shared_ptr, weak_ptr, Memory leak, Smart pointers.