Sometimes a resource must be shared by multiple parts of your application. You cannot use a unique pointer here because ownership must be shared. C++ solves this using std::shared_ptr, which manages resources using reference counting.
Let's look at reference counting, the physical layout of shared pointers in RAM, and the shared_from_this trap.
Reference Counting: Collaborative Lifecycles
A std::shared_ptr tracks how many shared pointers reference the same resource. When you copy a shared pointer, the reference counter increments. When a shared pointer goes out of scope or is destroyed, the counter decrements. The moment the reference counter drops to zero, the resource is automatically deleted from the heap.
- The Shared Document Metaphor: A shared pointer is like a collaborative document with a user counter. The document stays open as long as at least one user is viewing it (the user count is greater than zero). The absolute second the last user closes the document tab (count hits zero), the document is deleted from the server.
The Physical Layout: Two Pointers in RAM
A std::shared_ptr commonly stores a pointer to the managed object and a pointer to a control block. The standard does not mandate a specific representation or size, so treat this as a useful mental model rather than an ABI guarantee:
- A raw pointer pointing to the managed object in memory.
- A raw pointer pointing to a dynamically allocated helper structure called the Control Block.
The Control Block lives on the heap and contains:
- The strong reference count (tracks active shared pointers).
- The weak reference count (tracks active weak pointers).
- Custom deleters or allocators if provided.
When shared pointers to the same control block are used across threads, implementations synchronize reference-count updates. Copying or destroying a shared_ptr can therefore cost more than a non-owning reference or unique_ptr, especially under contention, but exact mechanics and overhead are implementation- and workload-dependent. Pass it by value only when the API intentionally shares ownership.
The Critical Trap: `enable_shared_from_this`
If a class object managed by a std::shared_ptr needs to pass a shared pointer of itself to a function, a common mistake is creating a new shared pointer using the this pointer directly. Do not do this!
Creating a new shared pointer from this allocates a second, completely independent control block. When both control blocks think their reference count has hit zero, they will both call delete on the same object, causing a double free crash.
To fix this trap, inherit from std::enable_shared_from_this<T> and call shared_from_this() to retrieve the original control block:
The Aliasing Constructor
There is an advanced shared pointer feature known as the aliasing constructor:
std::shared_ptr<U>(const std::shared_ptr<T>& parent, U* member)
This constructor creates a shared pointer that points to a specific sub object (like a member variable inside a struct) but shares the exact same control block of the parent object. As long as you hold the shared pointer to the member variable, the reference count of the entire parent object remains greater than zero, keeping the parent memory safe from deletion. This is highly useful for parsing zero copy networking streams.