Smart pointers are RAII applied to heap memory. They own a pointer and delete it automatically when they go out of scope. They are the reason modern C++ almost never leaks. All live in <memory>.
unique_ptr — sole ownership
A unique_ptr owns its object exclusively. It cannot be copied (that would mean two owners), only moved (ownership transfers). When it dies, it deletes. Zero runtime overhead versus a raw pointer — it's the default choice.
#include <memory>
auto p = std::make_unique<int>(42); // allocates, owns an int(42)
std::cout << *p; // use like a normal pointer
// auto q = p; // ERROR: cannot copy a unique_ptr
auto q = std::move(p); // OK: ownership moves to q; p is now null
// no delete needed — freed automatically when the owner diesshared_ptr — shared ownership
A shared_ptr lets multiple owners share one object. It keeps a reference count; the object is deleted when the last shared_ptr is destroyed. It costs more than unique_ptr (atomic counter updates), so use it only when ownership is genuinely shared.
auto a = std::make_shared<int>(7); // ref count = 1
{
auto b = a; // ref count = 2 (b shares ownership)
} // b dies, ref count = 1
// object freed when a dies and count hits 0weak_ptr — non-owning observer
A weak_ptr observes a shared_ptr's object without owning it — it doesn't affect the reference count. You lock() it to get a temporary shared_ptr if the object still exists. Its main job is breaking reference cycles.
If object A holds a shared_ptr to B and B holds a shared_ptr back to A, their counts never reach zero even when nothing else references them — a leak. Break the cycle by making one direction a weak_ptr. This is a classic interview question.
Always prefer the make_ functions
// GOOD
auto p = std::make_unique<Widget>(args);
auto s = std::make_shared<Widget>(args);
// AVOID: naked new is exception-unsafe in some call contexts and more verbose
std::unique_ptr<Widget> p2(new Widget(args));make_shared also allocates the object and its control block together, which is faster and cache-friendlier.
The type you choose documents who owns what. A function taking unique_ptr<T> by value says "I take ownership." Taking T* or T& says "I just use it, I don't own it." Taking shared_ptr<T> says "I share ownership." Get in the habit of reading signatures as ownership contracts.
Exercise 06.1 — Ownership in practice
- Write a
Noisytype (prints on construct/destruct). Store one in aunique_ptrinside a function and confirm it's destroyed at scope exit with no manual delete. - Move the
unique_ptrinto another and confirm the original becomes null (compare againstnullptr). - Create a
shared_ptr, copy it into a nested scope, and printuse_count()before, during, and after to watch the count rise and fall. - Build a deliberate cycle with two
shared_ptrs and confirm the destructors never run (a leak). Then fix it with aweak_ptrand watch them run.
Takeaways
unique_ptr: sole owner, move-only, zero overhead. The default.shared_ptr: shared ownership via atomic ref count. Use only when truly shared.weak_ptr: non-owning observer;lock()to use; breaks cycles.- Two
shared_ptrs pointing at each other leak — break withweak_ptr. - Use
make_unique/make_shared. Signatures encode ownership; read them that way.
Loading starter code…
Checkpoint · Smart Pointers
6 questions · pass at 70%
std::unique_ptr<T> (by value) communicates:Finished Module 06?
Pass the quiz to complete it automatically.