std::shared_ptr
A reference-counted smart pointer that enables shared ownership of a dynamically allocated object, deleting it when the last owner is destroyed.
std::shared_ptrsince C++11A smart pointer that retains shared ownership of an object through a reference count, destroying the managed object when the last shared_ptr owning it is destroyed or reset.
Overview
std::shared_ptr<T> implements shared-ownership semantics via reference counting. Every shared_ptr that owns an object shares a control block: a heap-allocated structure tracking the strong reference count, a weak reference count (for std::weak_ptr), and an embedded deleter and allocator. When the strong count falls to zero, the managed object is destroyed. The control block itself persists until the weak count also reaches zero.
Two construction strategies exist: constructing from a raw new expression, or using std::make_shared. Prefer make_shared in nearly all cases — it performs a single allocation for both the object and the control block, improving cache locality and eliminating an allocation. Direct construction from new requires two separate heap allocations.
// Two allocations: one for Widget, one for control block
std::shared_ptr<Widget> sp1(new Widget(42));
// Single allocation: object and control block are colocated (C++11)
auto sp2 = std::make_shared<Widget>(42);The reference count operations are atomic (typically using std::atomic internally), so copying and destroying shared_ptr instances from different threads is safe. The managed object itself is not protected — concurrent access to the pointed-to data requires external synchronization.
Syntax
#include <memory>
// Construction
std::shared_ptr<T> p1; // null, default-constructed
std::shared_ptr<T> p2(new T(args...)); // owns raw pointer
auto p3 = std::make_shared<T>(args...); // preferred (C++11)
auto p4 = std::make_shared<T[]>(n); // array support (C++20)
// Copying and moving
std::shared_ptr<T> p5 = p3; // increments refcount
std::shared_ptr<T> p6 = std::move(p3); // transfers ownership, p3 becomes null
// Observers
p5.use_count(); // strong reference count (approximate in multithreaded code)
p5.get(); // raw pointer, non-owning
p5.unique(); // true iff use_count() == 1 (deprecated in C++17, removed C++20)
static_cast<bool>(p5); // false iff null
// Modifiers
p5.reset(); // releases ownership
p5.reset(new T(args...)); // releases old, takes new
// Casts (C++11)
std::static_pointer_cast<Derived>(base_sp);
std::dynamic_pointer_cast<Derived>(base_sp); // returns null if cast fails
std::const_pointer_cast<T>(const_sp);
std::reinterpret_pointer_cast<U>(sp); // C++17Examples
Basic shared ownership
#include <memory>
#include <vector>
#include <iostream>
struct Node {
int value;
explicit Node(int v) : value(v) {
std::cout << "Node(" << v << ") constructed\n";
}
~Node() {
std::cout << "Node(" << value << ") destroyed\n";
}
};
void inspect(std::shared_ptr<Node> sp) { // copy: refcount +1
std::cout << "use_count inside: " << sp.use_count() << '\n';
}
int main() {
auto sp = std::make_shared<Node>(10); // refcount = 1
inspect(sp); // refcount = 2 inside, back to 1
{
auto sp2 = sp; // refcount = 2
std::cout << sp.use_count() << '\n'; // 2
} // sp2 destroyed, refcount = 1
// sp destroyed here -> Node(10) destroyed
}Custom deleters
shared_ptr accepts a callable deleter at construction time, stored type-erased in the control block. The deleter is part of the runtime state, not the type — unlike unique_ptr<T, Deleter>.
// File handle managed with custom deleter
auto file_deleter = [](FILE* f) {
if (f) std::fclose(f);
};
std::shared_ptr<FILE> file(std::fopen("data.bin", "rb"), file_deleter);
if (!file) throw std::runtime_error("failed to open");
// Use file.get() for C API calls
std::fread(buffer, 1, size, file.get());
// File closed automatically when last shared_ptr is destroyedAliasing constructor
The aliasing constructor (C++11) lets a shared_ptr<U> share ownership with a shared_ptr<T> while pointing to a different object — typically a member. The managed object (and its lifetime) is governed by the original shared_ptr<T>, but the aliasing pointer stores a different raw address.
struct Config {
std::string host;
uint16_t port;
};
auto cfg = std::make_shared<Config>("localhost", 8080);
// shared_ptr to member — keeps cfg alive, points at cfg->port
std::shared_ptr<uint16_t> port_ptr(cfg, &cfg->port);
cfg.reset(); // cfg's refcount drops to 1 (port_ptr holds it)
std::cout << *port_ptr << '\n'; // safe: Config still aliveThis pattern is used in std::shared_ptr implementations of observer registration and in certain container nodes.
enable_shared_from_this
When a member function needs to produce a shared_ptr to *this, inheriting from std::enable_shared_from_this<T> is the correct approach. Never construct a shared_ptr directly from this — that creates a second, independent control block, leading to a double-free.
#include <memory>
class Connection : public std::enable_shared_from_this<Connection> {
public:
static std::shared_ptr<Connection> create() {
return std::shared_ptr<Connection>(new Connection);
}
std::shared_ptr<Connection> self() {
return shared_from_this(); // correct: shares the existing control block
}
private:
Connection() = default;
};
// WRONG: creates two independent control blocks -> double-free
// Connection c;
// auto sp1 = std::make_shared<Connection>(c); // no
// auto sp2 = std::shared_ptr<Connection>(&c); // noThe base class stores a weak_ptr that is set when the first shared_ptr to the object is constructed — which is why construction must go through a factory or make_shared.
Best Practices
Use make_shared by default. A single allocation for object and control block is faster, more cache-friendly, and exception-safe in the general case.
Pass shared_ptr by value only when sharing ownership. If a function merely uses the object without extending its lifetime, accept const T& or T* instead. Copying a shared_ptr is not free — atomic increment/decrement has measurable cost on multi-core systems under high contention.
// Prefers non-owning access; caller decides lifetime
void process(const Widget& w);
void process(Widget* w); // nullable variant
// Use this only when the callee genuinely needs to share ownership
void register_handler(std::shared_ptr<Handler> h);Break ownership cycles with std::weak_ptr. A weak_ptr observes a shared_ptr-managed object without participating in its reference count. Promote it with lock(), which returns a shared_ptr or null if the object was already destroyed.
struct Parent {
std::vector<std::shared_ptr<Child>> children;
};
struct Child {
std::weak_ptr<Parent> parent; // NOT shared_ptr — avoids cycle
void notify() {
if (auto p = parent.lock()) {
// p is a shared_ptr, Parent is still alive
}
}
};Prefer unique_ptr as the default. shared_ptr carries overhead — two pointer-sized members plus a heap-allocated control block. Reach for it only when lifetime genuinely cannot be statically scoped or tied to a single owner.
Common Pitfalls
Constructing from the same raw pointer twice creates two control blocks with independent counts. Both will attempt to delete the object.
Widget* raw = new Widget;
std::shared_ptr<Widget> sp1(raw);
std::shared_ptr<Widget> sp2(raw); // undefined behavior: two control blocksuse_count() is unreliable for synchronization decisions. In multithreaded code, the count can change between observation and action. Use unique() (removed in C++20) or redesign the ownership model instead of polling the count.
make_shared and custom deleters don't mix. make_shared allocates object and control block together; a custom deleter requires separate allocation. Use the constructor form when a non-default deleter is needed.
The managed object outlives the control block if weak_ptr references exist. When using make_shared, the object's storage is part of the same allocation as the control block. The memory cannot be freed until both the strong and weak counts reach zero — even after the object itself is destroyed. For large objects with long-lived weak_ptr observers, two-argument construction (shared_ptr<T>(new T(...))) avoids this.
Avoid raw this captures in lambdas stored in shared_ptr-owned objects. The lambda may outlive the object if stored elsewhere.
// Dangerous: raw this captured in async callback
void Widget::schedule() {
enqueue([this] { this->update(); }); // Widget may be gone by the time callback fires
}
// Safe: capture by shared_ptr via shared_from_this()
void Widget::schedule() {
auto self = shared_from_this();
enqueue([self] { self->update(); });
}See Also
std::unique_ptr— exclusive ownership, zero overheadstd::weak_ptr— non-owning observer forshared_ptr-managed objectsstd::make_shared— preferred factory forshared_ptrstd::enable_shared_from_this— safeshared_ptrfromthis- RAII idiom — the ownership model underlying smart pointers