C++ - Smart Pointers & RAII

Overview

Estimated time: 60–80 minutes

Manage resources safely with RAII. Use smart pointers to express ownership. Prefer composition and automatic cleanup over manual new/delete.

Learning Objectives

  • Choose unique_ptr for sole ownership; shared_ptr/weak_ptr for shared lifetimes.
  • Use make_unique/make_shared and custom deleters.
  • Design RAII types to manage resources and invariants automatically.

Prerequisites

unique_ptr basics

#include <memory>
#include <iostream>
int main(){
  auto p = std::make_unique<int>(42);
  std::cout << *p << "\n";
  auto q = std::move(p); // transfer ownership
  std::cout << (p ? "p set" : "p null") << "\n";
}

Expected Output: 42 p null

shared_ptr and weak_ptr

#include <memory>
#include <iostream>
int main(){
  std::shared_ptr<int> a = std::make_shared<int>(7);
  std::weak_ptr<int> w = a;
  std::cout << a.use_count() << "\n"; // 1
  if (auto s = w.lock()) std::cout << *s << "\n"; // 7
}

Custom deleter

#include <cstdio>
#include <memory>
struct FileCloser { void operator()(std::FILE* f) const { if (f) std::fclose(f); } };
int main(){
  std::unique_ptr<std::FILE, FileCloser> f(std::fopen("data.txt", "w"));
  if (f) std::fputs("hello", f.get());
} // RAII closes file

Design guidelines

  • Prefer unique_ptr for ownership; use shared_ptr only when sharing lifetime is required.
  • Avoid cycles with shared_ptr; break with weak_ptr.
  • Favor make_unique/make_shared to reduce allocation overhead and exceptions hazards.

Beginner Boosters

#include 
#include 
std::unique_ptr make_counter(){ return std::make_unique(0); }
void bump(std::unique_ptr c){ (*c)++; std::cout << *c << "\n"; }
int main(){
  auto c = make_counter();
  bump(std::move(c));            // prints 1
  std::cout << (c?"alive":"moved") << "\n"; // moved
}

Expected Output: 1 moved

#include 
#include 
int main(){
  std::weak_ptr w;
  {
    auto s = std::make_shared(5);
    w = s; // w observes s
  } // s destroyed
  std::cout << (w.lock() ? "alive" : "expired") << "\n";
}

Expected Output: expired

Common Pitfalls

  • Manually calling delete on raw pointers managed by smart pointers—never do this.
  • Creating shared_ptr cycles (A holds shared_ptr to B and B to A); memory leak until broken by weak_ptr.

Checks for Understanding

  1. When to prefer unique_ptr vs shared_ptr?
  2. How do you prevent shared_ptr cycles?
Show answers
  1. unique_ptr for single ownership; shared_ptr when multiple entities must share lifetime.
  2. Use weak_ptr for back-references or to break cycles.

Exercises

  1. Wrap a POSIX handle (FILE*/fd) into an RAII type using unique_ptr with a custom deleter.
  2. Build a simple graph node with shared_ptr neighbors and weak_ptr back-links to avoid cycles.