39 / 163 · C++11 · 11 min
shared_ptr: Control Blocks and Thread-Safety Boundaries
What shared_ptr shares is a set of destruction responsibilities, usually recorded in a control block with owner counts, a deleter, and related information. The control block lets different shared_ptr instances concurrently manage the same object, but it does not protect the object’s internal data, and it does not allow unsynchronized modification of the same shared_ptr variable.
In this lesson
The Same Address Is Not the Same Ownership
Copying a shared_ptr joins an existing ownership group; when the last strong owner releases, the agreed deleter is called. Typical implementations store strong and weak counts, a deleter, and allocation information in a control block, but that field layout and any fixed size of shared_ptr are not interface guarantees. Do not infer Standard requirements from two addresses in a debugger.
Constructing two shared_ptr objects separately from the same raw address creates two unaware ownership groups and usually double-deletes. You must copy the original shared_ptr, or use a conversion that keeps the same ownership group. The address from get is only an observation; it cannot be used to manufacture a separate destruction responsibility.
The Stored Pointer and the Managed Object Can Differ
Aliasing construction can make a shared_ptr point at a member while keeping the whole host object alive. In the example the field pointer points at Bundle::value, but what is owned is Bundle’s shared lifetime. After the outer owner is reset, the field pointer still keeps the host alive until it too is reset.
make_shared commonly allocates the object and the control block together, reducing allocation count. When the last strong reference disappears the object is still destroyed by the rules, but if a weak_ptr remains the control block must stay; the combined storage may therefore be returned later. Recognize this cost when a large object meets a long-lived weak observer.
Thread Safety Has Three Object Layers
Different shared_ptr instances that share the same control block may be copied or reset on different threads without extra protection of the reference count. If the same shared_ptr variable is modified by one thread and accessed by another at the same time, you need a lock, or C++20’s atomic<shared_ptr<T>>.
The third layer is T itself: two legitimate shared_ptr objects writing an ordinary integer at the same time can still data-race. The control block only maintains lifetime; it does not lock T. use_count in a concurrent setting is only a snapshot observation and cannot assert exclusive modification rights; the example’s count assertions are only for a deterministic scene with no other threads.
Pitfalls
const shared_ptr<T>only restricts the handle and usually still allows modifying T; use shared_ptr<const T> when you need a read-only access type.- use_count equal to one is not a synchronization protocol; another thread may obtain a new owner by locking a weak pointer.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <memory>
struct Bundle {
static int alive;
int value;
Bundle() : value(42) { ++alive; }
~Bundle() { --alive; }
};
int Bundle::alive = 0;
int main() {
auto owner = std::make_shared<Bundle>();
std::shared_ptr<int> field(owner, &owner->value);
assert(owner.use_count() == 2);
owner.reset();
assert(Bundle::alive == 1);
assert(*field == 42);
field.reset();
assert(Bundle::alive == 0);
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread memory-shared-ptr.cpp -o example && ./exampleExpected result
Expected: exit 0, no output; every assert holds.
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Two threads each hold a copy of shared_ptr<Counter> and both execute ++p->value. Is reference-count safety enough?
Show a reference answer
No. Control-block operations on the two handles are guaranteed, but if value is an ordinary integer the two unsynchronized writes are a data race. Give Counter’s update operations the same mutex, or design the independent counter as a suitable atomic type. atomic<shared_ptr<Counter>> only solves publishing and replacing the handle; it does not automatically make Counter’s fields atomic.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.