Smart Pointers

Course 2 · Ch 4
Smart Pointers
The chapter cpp1-5's own tip-box pointed toward — and where C++'s and Rust's ownership models genuinely converge

cpp1-5's own Challenge 2 built a leak on purpose: a Circle* allocated with new, never deleted, because RAII only protects objects whose own lifetime is tied to a scope — and a raw pointer variable's scope has nothing to do with the heap object it points at. This chapter is the real fix.

The Gap Raw Pointers Leave Open

A raw pointer going out of scope destroys only the pointer itself, never the object it points to. Smart pointers close this gap by wrapping the raw pointer inside an RAII-managed object — exactly cpp1-5's own idea, applied specifically to ownership.

unique_ptr — Sole Ownership

#include <memory> std::unique_ptr<Circle> c = std::make_unique<Circle>(5.0); // c is automatically deleted when it goes out of scope — no explicit delete, ever

Exactly one unique_ptr owns a given object at a time. When it's destroyed, it automatically deletes the object it owns — genuinely just cpp1-5's own IntArray wrapper, generalized and already written for you.

unique_ptr Cannot Be Copied

A real, deliberate constraint: copying a unique_ptr would mean two owners both believing they're responsible for deleting the same object — exactly the double-free class from c2-3's own catalog. The compiler enforces this directly; attempting to copy one is a compile error, not a runtime risk.

std::unique_ptr<Circle> c2 = c; // compile error — unique_ptr cannot be copied std::unique_ptr<Circle> c2 = std::move(c); // fine — ownership transfers; c is now empty

Ownership can be transferred via std::move — a direct preview of cpp2-5's own Move Semantics chapter. After the move, the source unique_ptr becomes empty.

shared_ptr — Shared Ownership via Reference Counting

std::shared_ptr<Circle> a = std::make_shared<Circle>(5.0); std::shared_ptr<Circle> b = a; // fine — both now share ownership; reference count is 2

Multiple shared_ptrs can jointly own the same object, tracked by a hidden reference count. The object is deleted only when the last owning shared_ptr is destroyed.

The Real Comparison to Rust

unique_ptr is essentially Rust's Box<T> — sole ownership, moved not copied, deterministic destruction. shared_ptr is essentially Rust's Rc<T> — reference-counted shared ownership, the identical strategy. This is genuinely another point of real convergence, alongside cpp1-5's RAII/Drop parallel and cpp2-1's monomorphization parallel — C++'s and Rust's own ownership models visibly converge here.

A Genuine Gap Even Here — No Compile-Time Enforcement

The honest caveat: nothing stops a programmer from also keeping a raw pointer to the object a unique_ptr owns, and using that raw pointer after the unique_ptr is destroyed — a genuine use-after-free, still fully possible. Smart pointers eliminate the forgetful class of memory bug — nobody forgets to call delete anymore — not the deliberate-misuse class. Rust's borrow checker closes both.

ConceptRaw pointerunique_ptrshared_ptrRust
Ownershipnone — just an addresssole ownershared, ref-countedBox<T> / Rc<T>
Copy behaviorfreely copyablecompile error — must movecopyable — increments the countmove / Rc::clone
Compile-time misuse preventionnoneprevents double-ownership onlyprevents double-ownership onlyprevents use-after-free entirely
unique_ptr is the default; shared_ptr is the exception
Reach for unique_ptr first — reserve shared_ptr for cases where genuine shared ownership is actually needed, since reference counting carries real runtime overhead unique_ptr doesn't have at all.
A shared_ptr reference cycle leaks, despite being "smart"
Two objects each holding a shared_ptr to the other never reach a zero reference count between them — a genuine memory leak, smart pointers notwithstanding. std::weak_ptr exists specifically to break such cycles, a non-owning reference that doesn't contribute to the count.

Coding Challenges

Challenge 1

Rewrite cpp1-5's own Challenge 2 (a Circle allocated with new, never deleted) using std::unique_ptr instead, adding a destructor message to Circle to confirm it's now genuinely destroyed automatically.

📄 View solution
Challenge 2

Create two shared_ptrs jointly owning the same object, print the reference count after each is created (use_count()), then let one go out of scope and print the count again to observe it decrease.

📄 View solution
Challenge 3

Explain precisely why smart pointers are described as eliminating the "forgetful" class of memory bug but not the "deliberate misuse" class, giving a concrete scenario where a raw pointer kept alongside a unique_ptr still produces a genuine use-after-free.

📄 View solution

Chapter 4 Quick Reference

  • std::unique_ptr<T> — sole ownership, cannot be copied, transferred only via std::move
  • std::shared_ptr<T> — reference-counted shared ownership, deleted when the last owner is destroyed
  • unique_ptr ≈ Rust's Box<T>; shared_ptr ≈ Rust's Rc<T> — a real convergence in ownership model
  • Smart pointers eliminate forgotten-delete bugs, not deliberate raw-pointer misuse — Rust's borrow checker closes both
  • std::weak_ptr — a non-owning reference, the escape hatch for shared_ptr reference cycles
  • Next chapter: move semantics and rvalue references — the mechanism std::move already previewed here