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.
new/delete, no leaks
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.
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
-std=c++17+).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.
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
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.
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.
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
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.
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 }
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.
| You need… | Use |
|---|---|
| A single, clear owner (the common case) | std::unique_ptr + make_unique |
| Genuinely shared ownership | std::shared_ptr + make_shared |
To observe a shared_ptr without owning / break a cycle | std::weak_ptr |
| To just use an object you don't own (e.g. a parameter) | raw T* or T& — non-owning, totally fine |
unique_ptr?shared_ptr free its object?shared_ptrs to each other and never get freed. The fix?delete.unique_ptr in one line?make_unique.shared_ptr decide when to free?make_shared.weak_ptr for?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.unique_ptr; use shared_ptr only when ownership is truly shared; weak_ptr to observe/break cycles. No naked new/delete.T* or T& — that's non-owning access and is idiomatic.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.