Why a shared_ptr cycle leaks — and how weak_ptr fixes it

The key is separating the pointer handle from the heap object it points to.

⚠ Your exact question: "at } the structs are destroyed, so their shared_ptrs die too — why doesn't it work?"

At }, only the stack handles (main's local a and b) are destroyed. Each one dying just decrements a count by 1 — it deletes the heap object only if the count hits 0.

The other two shared_ptrs (A.b and B.a) live inside the heap objects, not on the stack. A heap object is destroyed only when its count reaches 0 — but that can't happen until the other object is destroyed first. Each keeps the other alive → nobody reaches 0.

Scene 1 — all shared_ptr, before }

Stack (main)

a : shared_ptr<A> ──▶ A
b : shared_ptr<B> ──▶ B

Heap

count 2Object A
A.b : shared_ptr<B> ──▶ B
  • held by: main.a (stack) + B.a (inside B)
count 2Object B
B.a : shared_ptr<A> ──▶ A
  • held by: main.b (stack) + A.b (inside A)

A is referenced by 2 pointers → count 2. Same for B.

Scene 2 — after } (all shared) leak

Stack (main) — destroyed at }

a : shared_ptr<A>
b : shared_ptr<B>

Heap — still alive!

count 1Object A
  • held by: B.a (inside B) only
count 1Object B
  • held by: A.b (inside A) only

Stuck. Each count dropped 2 → 1 (the stack handles are gone), but each object is still held by the other object's member. Neither reaches 0, so neither destructor runs, so those member pointers never die either. Both objects are unreachable but alive forever → memory leak.

Scene 3 — make B.a a weak_ptr fixed

Stack (main) — destroyed at }

a : shared_ptr<A>
b : shared_ptr<B>

Heap

count 1Object A
A.b : shared_ptr<B> ──▶ B
  • held by: main.a (stack) + B.a is weak → does NOT count
count 2Object B
B.a : weak_ptr<A> ┈┈▶ A (no count)
  • held by: main.b (stack) + A.b (inside A)

Cascade of cleanup: at } → main.a dies → A count 1 → 0 → A destroyed → A.b dies → B count 2 → 1 → main.b dies → B count 1 → 0 → B destroyed. No leak.

The one-line takeaway

A shared_ptr stored inside an object keeps the target alive as long as that object lives — and if two objects do this to each other, neither can ever die. weak_ptr observes without counting, so it breaks the deadlock.