C++ Standard Library · Lesson 10

Smart Pointers

Heap memory that frees itself. unique_ptr for a single owner, shared_ptr for shared ownership, weak_ptr to observe without owning — and the rule for which to reach for.

🎯 Own heap resources safely — no new/delete, no leaks
The one idea

A smart pointer is RAII (Lesson 1) applied to heap memory: it owns what it points at and frees it automatically. unique_ptr = exactly one owner (free, the default). shared_ptr = shared ownership via a reference count. weak_ptr = look without owning. Prefer unique_ptr; reach for shared_ptr only when ownership is truly shared.

01 The problem they kill: manual new/delete

Raw owning pointers force you to remember to delete — exactly once, on every path, including exceptions. Miss it and you leak; do it twice and you crash. Smart pointers move that bookkeeping into a destructor, so it just happens.

// the old way — every exit path must delete exactly once
Widget* w = new Widget();
// ... if anything throws or returns early here → LEAK ...
delete w;                       // easy to forget / double-do

// the modern way — freed automatically at end of scope
auto w = std::make_unique<Widget>();   // owns it; no delete, ever
Run on godbolt.org (-std=c++17+).
💡 The guideline is blunt Core Guidelines: no naked new/delete. Create owned heap objects with make_unique / make_shared. You'll rarely type new in modern C++.

Source: Core Guidelines R.11/R.20–R.23.

02 unique_ptr — one owner, zero overhead

The default smart pointer. It owns its object exclusively and deletes it when the unique_ptr dies. It's the same size and speed as a raw pointer — there's no reason to fear it. Because there can be only one owner, it can't be copied, only moved (ownership transfers).

#include <memory>

auto p = std::make_unique<Widget>(args...);   // construct + own
p->draw();                                    // use like a pointer
(*p).x;

// auto q = p;            ✗ won't compile — can't copy a unique owner
auto q = std::move(p);                        // ✓ transfer ownership; p is now null

// freed automatically when the owning unique_ptr goes out of scope
💡 Move = "hand over the deed" std::move(p) transfers ownership: q now owns the Widget and p is empty. This is move semantics — the efficient "transfer instead of copy" you met as a warning in Lesson 1, here made explicit and safe.

Source: cppreference — std::unique_ptr.

03 shared_ptr — shared ownership by reference count

When several owners must keep an object alive and you can't say which outlives the others, shared_ptr keeps a reference count. Each copy increments it; each destruction decrements it. When it hits zero, the object is freed.

Widget use_count: 3 shared_ptr a shared_ptr b shared_ptr c copy a shared_ptr → count++ destroy one → count–– count == 0 → object freed
The count lives in a small shared "control block." That extra block + atomic counting is why shared_ptr costs more than unique_ptr.
auto a = std::make_shared<Widget>();   // use_count == 1
auto b = a;                            // copy OK → use_count == 2
// when both a and b are gone, use_count hits 0 and Widget is freed
⚠ shared_ptr is not free, and not a default The control block, atomic refcount updates, and double allocation make it heavier than unique_ptr. Don't reach for it by habit — most objects have a clear single owner. Use it only when ownership is genuinely shared.

Source: cppreference — std::shared_ptr · Core Guidelines R.21 — prefer unique_ptr unless you need to share.

04 weak_ptr — observe without owning

Two shared_ptrs that point at each other create a reference cycle: each keeps the other's count above zero, so neither is ever freed — a leak. weak_ptr is a non-owning reference that breaks the cycle. It doesn't bump the count; to use it you lock() it, which gives a shared_ptr if the object is still alive (or null if it's gone).

std::shared_ptr<Node> parent = std::make_shared<Node>();
std::weak_ptr<Node>   back   = parent;     // does NOT raise use_count

if (auto sp = back.lock()) {                // promote to shared_ptr if alive
    sp->use();                            // safe to use here
} else {
    // the object was already freed
}
💡 The canonical use Parent owns children with shared_ptr; each child points back to its parent with a weak_ptr. Owning one direction and observing the other breaks the cycle while still letting children reach the parent.

Source: cppreference — std::weak_ptr.

05 Which pointer? (and when a raw pointer is fine)

You need…Use
A single, clear owner (the common case)std::unique_ptr + make_unique
Genuinely shared ownershipstd::shared_ptr + make_shared
To observe a shared_ptr without owning / break a cyclestd::weak_ptr
To just use an object you don't own (e.g. a parameter)raw T* or T& — non-owning, totally fine
🎯 The mental split: ownership vs access Smart pointers are about ownership (who frees it). A raw pointer/reference passed to a function that just reads the object is access, not ownership — still idiomatic and correct. Don't wrap a non-owning parameter in a smart pointer.

06 Check yourself

Which smart pointer is the default — cheap, single-owner?
Why can't you copy a unique_ptr?
When does a shared_ptr free its object?
Two objects hold shared_ptrs to each other and never get freed. The fix?

07 Flashcards

Q: What is a smart pointer, in terms of Lesson 1?
A: RAII applied to heap memory — it owns the pointee and frees it in its destructor, so no manual delete.
Q: unique_ptr in one line?
A: The default smart pointer: exactly one owner, zero overhead vs a raw pointer, move-only (can't be copied). Make it with make_unique.
Q: How does shared_ptr decide when to free?
A: It keeps a reference count in a control block; copies increment, destructions decrement, and the object is freed when the count reaches zero. Make it with make_shared.
Q: What is weak_ptr for?
A: A non-owning observer of a shared_ptr's object — it doesn't affect the count. Call lock() to get a shared_ptr if still alive. Used to break reference cycles.
Q: Default rule for choosing?
A: Prefer unique_ptr; use shared_ptr only when ownership is truly shared; weak_ptr to observe/break cycles. No naked new/delete.
Q: Should a read-only parameter take a smart pointer?
A: No. Smart pointers express ownership. To just use an object you don't own, take a raw T* or T& — that's non-owning access and is idiomatic.
👩‍🏫 I'm your teacher — ask me anything

Try: "why is make_shared better than shared_ptr<T>(new T)?", or "show me a tree where children weak-ptr back to the parent". Next: the small vocabulary types that glue APIs together.