Weak Pointers and Cyclic Memory Leaks (std::weak_ptr)
Smart Pointers and Memory Management

8.3 Weak Pointers and Cyclic Memory Leaks (std::weak_ptr)

While reference counting is excellent, it has a fatal flaw: circular references. If two objects hold shared pointers to each other, their reference counts can never hit zero, causing a permanent memory leak. C++ solves this by introducing std::weak_ptr.

Let's look at cyclic leaks and how weak pointers break the cycle.

The Circular Reference Leak Trap

Imagine a Parent class that holds a shared pointer to a Child, and the Child class holds a shared pointer back to the Parent. When the main function exits and destroys its shared pointers, the Parent still has a reference count of 1 (held by Child), and the Child has a reference count of 1 (held by Parent).

Because neither count can hit zero, their destructors are never called. The memory leaks permanently on the heap, despite using smart pointers!

Weak Pointers: The Non Owning Observer

A std::weak_ptr is a smart pointer that references a shared resource but does not increment the strong reference count. It only increments the weak reference count in the control block. If the strong reference count hits zero, the resource is deleted, even if weak pointers still reference it.

* The Spectator Metaphor: A weak pointer is like a spectator looking at a house. You can observe it, but you do not own it. If the owners decide to demolish the house (the strong reference count hits zero), they do not need your permission. The house is destroyed, and you are left looking at an empty plot.

Accessing Data Safely with `lock()`

Because the underlying object can be destroyed at any moment, you cannot dereference a std::weak_ptr directly. You must call lock() to convert it back to a std::shared_ptr first. If the object has been destroyed, lock() returns a null shared pointer:

#include <iostream>
#include <memory>

struct Parent;

struct Child {
    // Resolve cycle by holding weak pointer back to Parent instead of shared pointer!
    std::weak_ptr<Parent> parentRef;
    ~Child() { std::cout << "Child destroyed\n"; }
};

struct Parent {
    std::shared_ptr<Child> childPtr;
    ~Parent() { std::cout << "Parent destroyed\n"; }
};

int main() {
    {
        auto p{std::make_shared<Parent>()};
        auto c{std::make_shared<Child>()};
        
        p->childPtr = c;
        c->parentRef = p; // Cycle is broken!
    } // Both Parent and Child are destroyed successfully here!
    return 0;
}
Resolving circular leaks using std::weak_ptr.
Finished reading this lesson?