latency.lab
Module 06

Smart Pointers

Level: coreTime: ~90 minPrereq: Module 05

By the end you can

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.

unique.cpp
#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 dies

shared_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.

shared.cpp
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 0

weak_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.

The cycle problem

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

make.cpp
// 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.

Ownership is a design decision

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.

Build

Exercise 06.1 — Ownership in practice

  • Write a Noisy type (prints on construct/destruct). Store one in a unique_ptr inside a function and confirm it's destroyed at scope exit with no manual delete.
  • Move the unique_ptr into another and confirm the original becomes null (compare against nullptr).
  • Create a shared_ptr, copy it into a nested scope, and print use_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 a weak_ptr and 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 with weak_ptr.
  • Use make_unique/make_shared. Signatures encode ownership; read them that way.
Live

Loading starter code…

Checkpoint · Smart Pointers

6 questions · pass at 70%

Q01
Why can a unique_ptr be moved but not copied?
Whyunique_ptr represents sole ownership. Allowing a copy would mean two owners both trying to delete the same object. Moving transfers the single ownership.
Q02
When is a shared_ptr's object deleted?
Whyshared_ptr uses reference counting. The managed object is deleted when the final owning shared_ptr is destroyed and the count reaches zero.
Q03
Two objects each hold a shared_ptr to the other. What happens?
WhyMutual shared_ptrs form a cycle; neither count reaches zero, so neither is freed. Break the cycle by making one link a weak_ptr.
Q04
What does a weak_ptr contribute to the reference count?
Whyweak_ptr is a non-owning observer. It does not change the strong reference count; you call lock() to obtain a shared_ptr if the object still exists.
Q05
A function parameter of type std::unique_ptr<T> (by value) communicates:
WhyPassing unique_ptr by value transfers ownership into the function. Passing T& or T* would signal non-owning use; shared_ptr would signal shared ownership.
Q06
Why prefer make_shared over shared_ptr(new T)?
Whymake_shared performs a single allocation for object + control block, improving performance and locality, and avoids a subtle exception-safety gap of the raw-new form.

Finished Module 06?

Pass the quiz to complete it automatically.

+ Note