# C++ From Zero

> Learn C++ as its own language, not 'C with extras': the object model, RAII, value semantics, templates, the STL, and modern C++ - mental-model-first, from your first compile to why the Rule of Five exists.


---

# C++ From Zero

C++ gets introduced two ways, and both do it a disservice. Some call it "C with classes," which
undersells it - C++ has an entire object model, a memory-safety discipline, and a standard library
that C simply doesn't have. Others call it a kitchen sink of every feature ever proposed, which makes
it sound scarier than it is. The real story is calmer: C++ is built around one central idea - **tie a
resource's lifetime to an object's lifetime, and let the compiler manage the "letting go" for you.**
That idea is called RAII, and once you see it, the rest of the language reads as consequences of it.

This guide teaches C++ as its own language. We won't spend chapters translating from C - a little
prior programming helps, and if you already know C you'll recognize the syntax fast, but you don't
need it. What C++ adds on top - objects, constructors and destructors, references, templates, the
STL, exceptions, smart pointers - is the actual subject here, taught mental-model-first: before any
keyword or syntax, you'll understand what the thing *is* and why C++ works that way.

It's one zero-to-hero journey in two halves. **Phases 1-9 are the basics** - compiling, syntax, and
the object model, ending at the idea that makes C++ what it is: RAII, and the Rule of Five that falls
out of it. **Phases 10-17 are the deep half** - templates, the STL, smart pointers, inheritance,
exceptions, and modern C++, the stuff that separates "writes C++" from "understands C++." Each phase
carries a difficulty badge so you can see the climb.

If you've never programmed at all, start with a gentler on-ramp first -
[Programming From Zero](/guides/programming-from-zero) - then come back here. And if you already know
C, welcome: phase 2 is written for you, mapping exactly what changes and why, and
[C From Zero](/guides/c-from-zero) is there if you ever want the bare-metal half of this story, but
it's optional - this guide stands on its own.

## How to read this

- **Brand new to C++? Read 1-9 in order.** Phases 1-5 get you comfortable with the syntax and how
  C++ organizes a program. Then phases 6-8 are the heart of the language: classes, RAII, and the Rule
  of Five. Slow down there - it's worth it.
- **Already know C?** Read phase 2 closely - it's a direct map of what's different - then move
  normally through 3-5, which will feel familiar with new vocabulary. **Really slow down at phase 7.**
  RAII is the idea C doesn't have, and it changes how you think about every resource: memory, files,
  locks, sockets, all of it.
- **Coming from a garbage-collected language (Python, Java, JS)?** Phases 6-8 are where C++ diverges
  hardest from what you know. There's no garbage collector; instead, destructors and the Rule of Five
  do that job deterministically. Read those three phases as a unit.
- **Past the basics already?** Jump to the deep half - [Phase 10: Templates & Generic
  Programming](10-templates-and-generic-programming.md) onward is where RAII and value semantics grow
  up into generic code, the STL, smart pointers, and the parts of modern C++ that make it a genuinely
  different language from the one people warn you about.

## The phases

**Part 1 - The basics (🟢 Basic → 🟡 Intermediate)**
1. **[Compiling & Your First Program](01-compiling-and-your-first-program.md)** 🟢 - a compiler, `g++`/`clang++`, and what compiling a C++ program looks like.
2. **[From C to C++: What Changed](02-from-c-to-c-what-changed.md)** 🟢 - `iostream` over `printf`, `bool`, references, `new`/`delete`, and namespaces - the surface differences, named.
3. **[Types, Variables & Control Flow](03-types-variables-and-control-flow.md)** 🟢 - C++'s type system, `auto`, and control flow with C++'s small extra conveniences.
4. **[Functions, Overloading & Default Arguments](04-functions-overloading-and-default-arguments.md)** 🟢 - multiple functions sharing a name, default parameter values, and how overload resolution picks one.
5. **[References vs Pointers](05-references-vs-pointers.md)** 🟡 - what a reference actually is, when to reach for one over a pointer, and why C++ has both.
6. **[Classes & Objects](06-classes-and-objects.md)** 🟡 - bundling data with the functions that operate on it, `public`/`private`, and what "object" means in C++.
7. **[Constructors, Destructors & RAII](07-constructors-destructors-and-raii.md)** 🟡 - **the whole point of C++:** tying a resource's lifetime to an object's scope, with no garbage collector.
8. **[Copy, Move & the Rule of Five](08-copy-move-and-the-rule-of-five.md)** 🟡 - **the crux, part two:** what happens when an object owns a resource and gets copied, moved, or destroyed.
9. **[Operator Overloading](09-operator-overloading.md)** 🟡 - making your own types work with `+`, `==`, `<<`, and the rest, without it turning into magic.

**Part 2 - Beyond the basics (🔴 Advanced)**
10. **[Templates & Generic Programming](10-templates-and-generic-programming.md)** 🔴 - writing one function or class that works for any type, and what the compiler does with it.
11. **[The STL: Containers](11-the-stl-containers.md)** 🟡 - `vector`, `map`, `set`, `string`, and picking the right container instead of reinventing one.
12. **[The STL: Iterators & Algorithms](12-the-stl-iterators-and-algorithms.md)** 🟡 - the glue between containers and algorithms, and why `std::sort` beats a hand-written loop.
13. **[Smart Pointers & Modern Memory Management](13-smart-pointers-and-modern-memory-management.md)** 🔴 - `unique_ptr`, `shared_ptr`, and RAII applied to `new`/`delete` so you (almost) never call them.
14. **[Inheritance & Polymorphism](14-inheritance-and-polymorphism.md)** 🟡 - `virtual`, base and derived classes, and when inheritance is actually the right tool.
15. **[Error Handling: Exceptions and Alternatives](15-error-handling-exceptions-and-alternatives.md)** 🟡 - `try`/`catch`/`throw`, exception safety, and why some modern C++ avoids exceptions entirely.
16. **[Modern C++: auto, Lambdas, Ranges & What Changed Since C++11](16-modern-c-auto-lambdas-ranges-and-what-changed-si.md)** 🟡 - the features that make current C++ read nothing like the C++ of 15 years ago.
17. **[Undefined Behavior, Gotchas & Where to Go Next](17-undefined-behavior-gotchas-and-where-to-go-next.md)** 🔴 - the mistakes that compile fine and misbehave anyway, and what to build next.

> C++'s wider ecosystem (build systems, package managers, specific frameworks like Qt or game
> engines) is its own world - this guide makes the *language* make sense, top to bottom.


---

# Compiling & Your First Program

**What it actually is.** C++ is a *compiled* language: before your program can run, a separate program called a compiler translates your `.cpp` source file into machine code your CPU can execute directly. That's different from a language like Python or JavaScript, where an interpreter reads your source and executes it line by line, on the fly, every time you run it. In C++, that translation happens once, ahead of time, and produces a standalone executable file. Run it a thousand times and the compiler never runs again - you're just launching the machine code that was already built.

**Why this exists.** C++ was designed to get out of the way between your code and the hardware. Compiling ahead of time means the CPU never waits on an interpreter to figure out what your code means - it just runs the instructions. That's most of why C++ programs start instantly and run fast: the thinking about *what your code does* happened once, at compile time, not every time the program runs.

This trade shows up immediately once you write code: a compiled language can catch a whole category of mistakes - like a misspelled type or a function called with the wrong number of arguments - *before your program ever runs*, because the compiler has to fully understand your code's structure just to translate it. An interpreter often doesn't discover the same mistake until it stumbles onto that exact line while running. You'll feel this the first time the compiler refuses to build something that "looks fine" - it's not being difficult, it's catching a bug for free.

## Getting a compiler

You need a C++ compiler and, later, a build tool, but for this phase just the compiler. Pick based on your OS:

- **Linux:** `g++` is usually preinstalled or one `apt install build-essential` away.
- **macOS:** install Xcode Command Line Tools (`xcode-select --install`), which gives you `clang++`.
- **Windows:** install [MSYS2](https://www.msys2.org/) for `g++`, or Visual Studio's "Desktop development with C++" workload for MSVC's `cl`. This guide uses `g++`-style commands; the ideas transfer directly to any compiler.

Check it's there:

```console
$ g++ --version
g++ (GCC) 15.1.0
```

If that prints a version number, you're set. If it says "command not found," the install didn't put the compiler on your `PATH` - worth fixing before moving on, since every phase from here assumes a working compiler.

## Your first program

Create a file named `main.cpp`:

```cpp
#include <iostream>

int main() {
    std::cout << "Hello, C++!" << std::endl;
    return 0;
}
```

Compile and run it:

```console
$ g++ main.cpp -o hello -std=c++20
$ ./hello
Hello, C++!
```

*What just happened:* `g++ main.cpp -o hello` told the compiler "read `main.cpp`, produce an executable named `hello`." `-std=c++20` tells it which version of the C++ standard to compile against (more on why that matters in a moment). Nothing printed during compilation because nothing went wrong - a silent compile is a successful one. Then `./hello` actually ran the machine code that got produced.

Let's take the program apart line by line, because every piece here is a concept you'll use in every C++ file you ever write:

- **`#include <iostream>`** pulls in the declarations for input/output facilities - specifically `std::cout`, the object you write text to. C++ doesn't build I/O into the language itself; it's a library, and you have to explicitly ask for it. This line runs *before* compilation proper, in a step called preprocessing (more below).
- **`int main()`** is the function every C++ program starts running from. Returning `int` is how your program reports its exit status to whatever launched it - `0` conventionally means "everything succeeded." If you omit `return 0;` from `main` specifically, the compiler inserts it for you (a special-case rule that applies only to `main`), but writing it explicitly is good habit and required in every other function.
- **`std::cout << "Hello, C++!" << std::endl;`** writes text to standard output. `std::cout` is an object representing "the console"; `<<` is the *stream insertion operator*, read as "send this into that stream." `std::endl` sends a newline and flushes the output buffer. The `std::` prefix means "look inside the `std` namespace" - the container that holds everything the C++ standard library provides, keeping its names from colliding with yours. You'll see `std::` constantly; namespaces are how C++ avoids everyone's `vector` or `string` clashing with everyone else's.

## What the compiler actually does to your file

Knowing the four stages makes compiler errors far less mysterious, because each stage fails in its own recognizable way:

1. **Preprocessing.** Lines starting with `#`, like `#include`, run first and are pure text substitution - `#include <iostream>` literally pastes the contents of the `iostream` header into your file before anything else happens. There's no C++ understanding here yet, just text manipulation.
2. **Compiling.** The preprocessed text is parsed, type-checked, and turned into assembly/object code for one file at a time. This is where "cannot convert `int` to `std::string`"-style errors come from - the compiler now understands your code's meaning, not just its text.
3. **Assembling.** The assembly output is turned into an object file (`.o` on Linux/macOS, `.obj` on Windows) - machine code, but not yet a runnable program.
4. **Linking.** The linker stitches your object file(s) together with the library code they depend on (like the implementation of `std::cout`) into one final executable. This is where "undefined reference to `foo`" errors come from: the compiler understood a name existed, but nothing anywhere actually defines it.

For now, `g++ main.cpp -o hello` runs all four stages in one command. Once programs span multiple files (phase 5 territory in most guides, but not this one), you'll separate compiling and linking into distinct steps - but the four stages are always happening, whether you see them or not.

## Which standard version, and why it matters

That `-std=c++20` flag matters more than it looks. C++ isn't one fixed language - it's revised by an international standards committee roughly every three years (C++11, 14, 17, 20, 23...), and each revision adds real language features, not just library additions. Leave the flag off and your compiler silently falls back to whatever its own default is, which varies by compiler and version - a common source of "it compiles on my machine but not yours." Pick a standard explicitly and stay consistent across a project. This guide targets C++17/20 idioms throughout, so `-std=c++20` (or `-std=c++17` if your compiler is older) is a safe default to reach for from here on.

## A worked example: reading input

One more small program, this time reading from the user, since almost everything you build will need to:

```cpp
#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "What's your name? ";
    std::cin >> name;
    std::cout << "Hello, " << name << "!" << std::endl;
    return 0;
}
```

```console
$ g++ main.cpp -o greet -std=c++20
$ ./greet
What's your name? Ada
Hello, Ada!
```

*What just happened:* `std::cin` is the input counterpart to `std::cout` - `>>`, the *stream extraction operator*, pulls a value out of it into `name`. `std::cin >> name` reads one whitespace-delimited "word" at a time; type `Ada Lovelace` and `name` would only capture `Ada`. That's not a bug to work around yet - just something to notice, because it'll matter once you're reading full lines in a later phase.

## Two errors you'll definitely see soon

Recognizing these now saves you a confused afternoon later:

```console
$ g++ main.cpp -o hello -std=c++20
main.cpp:2:10: error: 'cout' is not a member of 'std'
    std::cout << "Hello, C++!" << std::endl;
         ^
```
This means you forgot `#include <iostream>` - the compiler has no idea what `std::cout` is because you never asked for its declaration.

```console
$ g++ main.cpp -o hello -std=c++20
main.cpp:4:44: error: expected ';' before 'return'
```
A missing semicolon. C++ statements end with `;`. Notice the error *names* `return` - the next statement - even though the fix belongs on the line above: the compiler only noticed the missing `;` once it reached the following token. When an error points at a token that looks perfectly fine, suspect the end of the line just before it.

## Recap

1. C++ compiles ahead of time into a standalone executable - no interpreter runs your program, which is a big part of why it's fast and catches errors before runtime.
2. Compilation is four stages: preprocessing (text substitution), compiling (parsing + type-checking), assembling (machine code), and linking (stitching in library code) - most compiler errors map cleanly onto one of these.
3. `#include`, `std::`, `std::cout`/`std::cin`, and `main`'s `return 0` are the five things every C++ file has, and now you know what each one is actually doing.
4. Always pick a `-std=` flag explicitly instead of trusting your compiler's default.

Compiling ahead of time is only half the story of why C++ feels so different from languages you may already know. The bigger half is what phase 2 covers: C++ isn't "C plus some extra syntax" - it's a genuinely different way of thinking about programs, built around objects and RAII instead of just functions and manual memory management.

### Check yourself

Test yourself on the ideas this phase depends on - what compiling ahead of time actually buys you, and where in the pipeline different errors come from:

```quiz
[
  {
    "q": "Why does a C++ program you've already compiled start up faster than an equivalent script run by an interpreter?",
    "choices": [
      "The compiler already turned the source into machine code once, so running it is just executing instructions, not translating them on the fly",
      "Compiled executables are always smaller files than source code",
      "C++ programs don't use a CPU the way interpreted languages do",
      "The compiler removes all function calls before producing the executable"
    ],
    "answer": 0,
    "explain": "Compiling is a one-time translation step; every later run just executes the already-produced machine code, with no translation work happening at run time."
  },
  {
    "q": "You get `error: 'cout' is not a member of 'std'`. Which stage caught this, and what does that tell you?",
    "choices": [
      "Compiling - the compiler understood your code's structure well enough to know `std::cout` was never declared, most likely a missing `#include <iostream>`",
      "Preprocessing - `#include` lines are checked for typos before anything else runs",
      "Linking - the executable was built but couldn't find the `cout` symbol at load time",
      "Assembling - the assembler doesn't recognize `cout` as valid machine code"
    ],
    "answer": 0,
    "explain": "This is a compiling-stage error: the compiler knows what `std::cout` would mean but never saw it declared, which almost always means the `#include` that provides it is missing."
  },
  {
    "q": "You leave off the `-std=` flag entirely. What actually happens?",
    "choices": [
      "The compiler silently falls back to whatever its own default standard version is, which can differ across compilers and versions",
      "Compilation fails immediately with an error demanding a standard version",
      "The compiler automatically detects and uses the newest C++ standard it supports",
      "It compiles as plain C instead of C++"
    ],
    "answer": 0,
    "explain": "Without an explicit `-std=`, you get whatever default your specific compiler ships with - a common source of code that compiles on one machine but not another."
  }
]
```


---

# From C to C++: What Changed

Here's a claim that sounds harmless and is actually wrong: "C++ is C with some extra features bolted on." That framing will slow you down for months. C++ *can* compile most C code, and it *does* keep C's low-level control, but the two languages solve the same problem - talk directly to the machine - with different philosophies. C trusts you completely and gives you a small, sharp set of tools. C++ trusts you too, but it also gives you a type system that argues with you before your program runs, and a set of abstractions designed to cost nothing at runtime once compiled. If you know C - maybe from [C From Zero](/guides/c-from-zero) - this phase is your bridge: the same close-to-the-machine world, but with the compiler doing more work on your behalf.

None of what's in this phase is the "big idea" of C++. That's still three phases away, in [classes and objects](06-classes-and-objects.md) and then [RAII](07-constructors-destructors-and-raii.md). This phase is the smaller, more immediate stuff: the changes you'll bump into in the very first C++ programs you write, before you've touched a single class.

## The mental model: a stricter, richer type system

**What actually changed.** C's compiler is permissive. It'll let a `char*` sneak into an `int` parameter with a warning at worst. C++'s compiler is a stricter reader: it checks more, infers more, and refuses more. Every change in this phase is really one change wearing different outfits: **C++ pushes decisions from "figure it out at runtime, or crash" to "the compiler catches it before your program exists."**

That's the same spirit you'll later see taken to its extreme with ownership rules in Rust or borrow checking elsewhere - C++ is an earlier, more permissive point on that same spectrum: more safety than C, less than languages built decades later with hindsight.

## Comments, `bool`, and small syntax gifts

Trivial but worth naming: C++ added `//` single-line comments (later adopted back into C99) and a real `bool` type with `true`/`false`, instead of C's convention of using `int` and treating zero as false.

```cpp
bool is_ready = true;   // a real type, not an int pretending to be one
// this whole line is a comment
```

In C, you'd write `int is_ready = 1;` and hope everyone remembers what `1` means. Small, but it sets the tone: C++ prefers a type that says what it is.

## References: a name that IS the variable

C only gives you pointers for indirect access. C++ adds **references** - full depth is [Phase 5: References vs Pointers](05-references-vs-pointers.md), but the shape of the idea belongs here, because it changes how you write ordinary functions starting today.

```cpp
void increment(int& n) {   // n is another name for the caller's variable
    n = n + 1;
}

int main() {
    int x = 10;
    increment(x);   // no & needed at the call site - not a pointer
    // x is now 11
}
```

**Why this exists.** In C, to let a function modify the caller's variable, you pass a pointer and dereference it everywhere (`*n = *n + 1`), and the caller must remember to pass `&x`. A reference is a name that's bound to a variable at creation and can never be reseated to point elsewhere and can never be null (barring deliberate abuse) - so the compiler can let you use it exactly like the original variable, no `*` needed. It's the same indirection under the hood, with a safer, simpler surface on top.

## Function overloading and default arguments

C requires one name per function; C++ lets several functions share a name if their parameter types differ, and lets a parameter have a fallback value. Both get a full phase ([Phase 4](04-functions-overloading-and-default-arguments.md)); here's the shape:

```cpp
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }   // same name, different types

void greet(std::string name = "friend") {          // default argument
    std::cout << "Hello, " << name << "!\n";
}
```

In C you'd be forced into distinct names like `add_int` and `add_double`. C++'s compiler picks the right `add` by matching argument types at compile time - this is **static binding** (the target is fixed when the program is built, not chosen at runtime like a virtual call), and it costs nothing at runtime.

## `new`/`delete` instead of `malloc`/`free`

C's dynamic memory is untyped: `malloc` hands back a `void*` of raw bytes, and you're responsible for the size math and the cast.

```c
/* C */
int* arr = malloc(5 * sizeof(int));
free(arr);
```

```cpp
// C++
int* arr = new int[5];
delete[] arr;
```

**Why this exists.** `new` knows the type you asked for, so it computes the size itself and returns a correctly-typed pointer - no `sizeof`, no cast. More importantly, `new` calls the type's *constructor* if it has one, and `delete` calls its *destructor* - `malloc`/`free` know nothing about construction, they just reserve and release bytes. That hook is the seed of [RAII](07-constructors-destructors-and-raii.md), the idea that will end up being the crux of this whole guide. For now, just remember the rule: anything you `new`, you `delete`; anything you `new[]`, you `delete[]`. Mismatching them is undefined behavior.

In practice, modern C++ code barely calls `new`/`delete` directly at all - [Phase 13](13-smart-pointers-and-modern-memory-management.md) shows the tools that manage it for you. Seeing raw `new`/`delete` now just gives you the vocabulary for what those tools are automating.

## Namespaces instead of one giant global bucket

C has exactly one bucket for every function and global name in a program - which is why C libraries prefix everything (`sqlite3_open`, `gtk_widget_show`) to avoid collisions. C++ adds **namespaces**, a way to group names so two libraries can each have a `parse()` without conflict.

```cpp
namespace shapes {
    double area(double r) { return 3.14159 * r * r; }
}

int main() {
    double a = shapes::area(2.0);   // :: reaches into the namespace
}
```

The standard library lives in namespace `std`, which is why you'll see `std::cout`, `std::string`, `std::vector` everywhere instead of bare names - that prefix is telling you "this comes from the standard library's namespace," not a special keyword.

## `iostream` instead of `stdio.h`

C's `printf("%d\n", x)` relies on a format string that the language doesn't require the compiler to check against your arguments - get the `%d` vs `%s` wrong and it's undefined behavior, not a guaranteed error (mainstream compilers do warn under `-Wformat`, but nothing forces them to). C++'s `<iostream>` uses overloaded operators instead of format strings:

```cpp
#include <iostream>
int x = 42;
std::cout << "x is " << x << "\n";
```

Every `<<` is resolved by the compiler based on the actual type of what's on its right, so there's no format string to get wrong. You'll still see `printf` in real C++ code - it's still valid and sometimes convenient - but `iostream` is the type-safe default.

## Casts you have to mean

C has one cast, `(int)x`, that will silently do almost anything you ask, safe or not. C++ splits this into named casts - `static_cast<int>(x)` for ordinary conversions, `const_cast`, `reinterpret_cast`, and `dynamic_cast` (once you have inheritance, in [Phase 14](14-inheritance-and-polymorphism.md)) - each one saying exactly what kind of danger you're opting into. It's more typing on purpose: a `reinterpret_cast` in a code review is a flag; a C-style `(int)` hides in plain sight.

## What still works exactly like C

Loops, `if`/`else`, arithmetic, arrays, `struct` layout, pointer arithmetic, the preprocessor - all of it carries over unchanged, because C++ was built to compile the vast majority of valid C. [Phase 3](03-types-variables-and-control-flow.md) covers the type and control-flow layer directly. The difference isn't that C stopped working - it's that C++ hands you sharper tools alongside the old ones, and idiomatic C++ reaches for them by default.

## Quick reference

| C | C++ | Why it changed |
|---|-----|-----------------|
| `int flag` (0/1) | `bool` | a type that says what it means |
| pointer + `*p` | reference `T&` | safer alias, no null, no reseating |
| one name per function | overloads by parameter type | lets related operations share a name |
| `malloc`/`free` | `new`/`delete` | typed, calls constructor/destructor |
| prefixed globals (`sqlite3_open`) | `namespace` + `::` | groups names, avoids collisions |
| `printf`/`scanf` | `<iostream>`, `std::cout`/`std::cin` | type-checked at compile time |
| `(int)x` | `static_cast<int>(x)` | names the kind of conversion you mean |

## Recap

1. C++ is not "C with extras" - it's the same low-level control with a stricter, richer type system layered on top, and every change here is a version of that one idea.
2. References (`T&`) give you pointer-like indirection with a name that can't be null or reseated - full depth in Phase 5.
3. Function overloading and default arguments let related functions share a name, resolved at compile time (Phase 4).
4. `new`/`delete` are typed and construction-aware, unlike `malloc`/`free` - the first hint of RAII, which shows up properly in Phase 7.
5. Namespaces (`std::`), `<iostream>`, and named casts (`static_cast`) all trade a little more typing for the compiler catching more mistakes before runtime.
6. Ordinary C - loops, arrays, structs, pointer arithmetic - still works in C++ unchanged; you're gaining tools, not losing the ones you know.

## Quick check

Test yourself on the idea that ties this whole phase together - the compiler doing more work before your program ever runs:

```quiz
[
  {
    "q": "A colleague says a C++ reference is \"just a pointer with nicer syntax - same thing under the hood, so treat them the same.\" What's the real distinction that matters?",
    "choices": [
      "A reference is bound to one variable for its whole life - it can't be reseated and can't be null, so the compiler can let you use it exactly like the original variable",
      "A reference is stored in a completely different part of memory than a pointer, which is why it's safer",
      "There's no real distinction - the claim is correct and references are only a stylistic choice",
      "A reference can be reseated to point at a different variable, unlike a pointer"
    ],
    "answer": 0,
    "explain": "The implementation may be similar, but the guarantee is what matters: a reference can't be null or rebound after creation, so it behaves like another name for the same variable rather than a separate indirect value."
  },
  {
    "q": "Why does the phase call `new`/`delete` a bigger change than just \"typed `malloc`/`free`\"?",
    "choices": [
      "Because `new` and `delete` also call the type's constructor and destructor, something `malloc`/`free` know nothing about",
      "Because `new` and `delete` are faster at runtime than `malloc`/`free`",
      "Because `new` and `delete` automatically free memory for you, so you never call `delete`",
      "Because `malloc`/`free` no longer work at all once you use C++"
    ],
    "answer": 0,
    "explain": "The size and cast bookkeeping `new` removes is a convenience, but the construction/destruction hook is the important part - it's the seed of RAII, which malloc/free have no concept of."
  },
  {
    "q": "When the compiler sees `add(3, 4)` and there are two overloads, `add(int, int)` and `add(double, double)`, when does it decide which one to call?",
    "choices": [
      "At compile time, by matching the argument types - this is static binding and costs nothing when the program runs",
      "At runtime, by checking the actual values passed in",
      "It can't decide, so it's a compile error unless you rename one of the functions",
      "Whichever overload appears first in the file is always called"
    ],
    "answer": 0,
    "explain": "Overload resolution happens at compile time based on argument types, so the correct `add` is baked into the binary before the program ever runs - there's no runtime lookup involved."
  }
]
```


---

# Types, Variables & Control Flow

Phase 2 showed you the shape of the shift from C to C++: same compiled, statically-typed foundation, but with an object model and stricter rules layered on top. This phase is where that shows up in the most ordinary code you'll write - declaring a variable, looping over a collection, branching on a condition. None of it is exotic. All of it is a little nicer than C, and a few pieces work in ways that will trip you if you assume C++ is "C with better syntax."

The mental model for this whole phase is simple: **C++ keeps C's control flow almost unchanged, but tightens and extends the type system around it.** You're not learning new control structures. You're learning where C++ refused to inherit C's looseness.

## `bool` is a real type now

In C, "true" and "false" started life as just nonzero and zero integers. C99 added a real boolean type, `_Bool` (available with no header), and `<stdbool.h>` gives it the readable names `bool`/`true`/`false`; C23 promotes those to built-in keywords. Even so, C's boolean still converts freely to and from `int`. In C++, `bool` is a built-in type from the start, with exactly two values: `true` and `false`. It's not an alias for `int`, and the compiler treats it differently for overload resolution (phase 4) and template deduction (phase 10) - a `bool` argument won't silently match an `int` overload the way it might in C.

```cpp
bool isReady = true;
bool hasError = false;

if (isReady && !hasError) {
    // ...
}
```

Comparisons (`==`, `<`, `&&`, and so on) now produce a genuine `bool`, not an `int` that happens to be 0 or 1. In practice you'll barely notice the difference day to day - `if (isReady)` reads the same either way - but it matters the moment you overload a function on `bool` vs `int`, which C simply can't express.

## Declaring variables: same rules, one new trick

Variable declarations look identical to C: `type name = value;`. What's new is **`auto`**, which tells the compiler "figure out the type from the initializer" instead of you spelling it out.

```cpp
int count = 5;          // ordinary, explicit
auto count2 = 5;        // also an int - deduced from 5
auto name = std::string("Ada");   // deduced as std::string
auto ratio = 3.14;      // deduced as double
```

**What `auto` actually is.** It's a compile-time placeholder, resolved once, at the declaration - there's no runtime cost and no dynamic typing involved. The compiler looks at the right-hand side, decides the type, and from then on `count2` is exactly as strongly typed as if you'd written `int` yourself. Nothing about the variable is flexible after that line.

**When to reach for it, and when not to.** `auto` earns its keep when the type is verbose or obvious from context - you'll see this constantly once you meet iterators (phase 12) and templates (phase 10), where the "real" type name can be an unreadable mouthful. It earns nothing, and actively costs readability, when spelling the type out is what makes the line clear:

```cpp
auto x = getValue();      // what is x? you can't tell without checking getValue()
int score = getValue();   // now the reader knows immediately
```

A reasonable habit for this early in your C++ life: write the type out explicitly by default, and use `auto` only where the type is either painfully long or already obvious from the right-hand side (like `auto name = std::string("Ada")`, where the type is right there in the constructor call).

## Control flow: C's structures, unchanged

`if`/`else`, `while`, `do...while`, the classic three-part `for`, and `switch` all work exactly like they do in C - same syntax, same semantics. If you've read [C From Zero](/guides/c-from-zero), phase 3 there covers this ground and nothing about it changed coming into C++.

```cpp
for (int i = 0; i < 5; ++i) {
    std::cout << i << " ";
}
```

C++ does add one new loop shape worth learning immediately, because you'll use it constantly once you meet the STL in phases 11-12: the **range-based `for`**.

```cpp
std::vector<int> nums = {10, 20, 30, 40};

for (int n : nums) {
    std::cout << n << " ";
}
```

**What it actually is.** `for (int n : nums)` walks every element of `nums` in order, binding `n` to a copy of each one in turn - no index variable, no bounds to get wrong, no off-by-one errors. It works on arrays, `std::vector`, `std::string`, and anything else that exposes `begin()`/`end()` (the STL containers in phase 11 all do this).

**Why the copy matters.** `for (int n : nums)` copies each element into `n`. For small types like `int` that's free, but for something expensive like a `std::string`, copying every element on every iteration is wasteful - and if you *modify* `n`, you're only modifying the copy, not the original. Fix both problems the same way you will everywhere else in C++: borrow instead of copy.

```cpp
for (const std::string& word : words) {   // read-only, no copies
    std::cout << word << "\n";
}

for (int& n : nums) {                     // reference: modifies the real element
    n *= 2;
}
```

References (`&`) get their full treatment in phase 5, but the pattern to recognize now is: `T` copies, `const T&` reads without copying, `T&` reads *and* writes the original. Reach for `const T&` as your default in a range-based `for` unless the type is tiny (`int`, `double`, `char`) or you genuinely need to mutate the elements.

## `switch`, mostly unchanged - with one sharper edge

`switch` in C++ behaves like C's: it jumps to the matching `case`, and execution *falls through* into the next case unless you `break`. That fall-through is exactly as much of a footgun in C++ as it is in C - the compiler still won't stop you from forgetting a `break`. C++ just adds one refinement worth knowing: you can declare a variable scoped to a single `case` by wrapping it in braces, since without them a variable declared in one `case` is technically visible (but not necessarily initialized) in the ones below it.

```cpp
switch (grade) {
    case 'A': {
        int bonus = 10;
        std::cout << "Excellent, bonus: " << bonus << "\n";
        break;
    }
    case 'B':
        std::cout << "Good\n";
        break;
    default:
        std::cout << "Keep going\n";
}
```

## `enum class`: the fix for C's leaky enums

C's `enum` has a real problem: its values leak into the surrounding scope as plain integers, and two different enums can silently collide or compare equal.

```c
enum Color { RED, GREEN, BLUE };
enum Fruit { APPLE, BANANA };
// RED is 0, APPLE is also 0 -- and both are really just `int`
if (RED == APPLE) { /* this compiles and is true, which makes no sense */ }
```

C++ fixes this with **`enum class`** (a "scoped enum"): its values live inside the enum's own namespace, and it does not implicitly convert to `int` or compare against unrelated enums.

```cpp
enum class Color { Red, Green, Blue };
enum class Fruit { Apple, Banana };

Color c = Color::Red;       // must qualify with Color::
// if (c == Fruit::Apple)   // compile error: not comparable, and it shouldn't be

if (c == Color::Red) {
    std::cout << "red\n";
}
```

**Why this exists.** The whole point of `enum class` is to catch exactly the bug the C example above lets through: comparing values that only *happen* to share a numeric representation but mean nothing to each other. You pay one small cost - `Color::Red` instead of bare `Red` - for a guarantee that the compiler, not a code reviewer, catches the mix-up. Old-style `enum` (without `class`) still exists in C++ for backward compatibility with C, but prefer `enum class` in new code; there's rarely a good reason to reach for the leaky version.

## Putting it together

None of this phase reinvents control flow - an `if` is still an `if`. What changed is precision: `bool` stops booleans from being a convention and makes them a type; `auto` removes repetition without removing static typing; range-based `for` removes an entire category of indexing bugs; `enum class` closes the door C left open on accidental enum comparisons. Small tightenings, each one removing a way to shoot yourself in the foot, which is the theme you'll see again and again as this guide goes deeper into C++'s object model starting next phase.

### Check yourself

```quiz
[
  {
    "q": "What does `enum class` actually change compared to a plain C-style `enum`?",
    "choices": [
      "It scopes the values inside the enum's own name and removes implicit conversion to int, so unrelated enums can no longer accidentally compare equal",
      "It makes the enum's values start counting from 1 instead of 0",
      "It lets the enum hold values of any type, not just integers",
      "It's just a stylistic alias for `enum`, with identical behavior at compile time"
    ],
    "answer": 0,
    "explain": "Plain enum values leak into the surrounding scope as bare ints, so two unrelated enums can compare equal by accident; enum class keeps values namespaced and non-convertible, so that comparison becomes a compile error instead."
  },
  {
    "q": "In `for (int n : nums) { n *= 2; }`, why does `nums` end up unchanged after the loop?",
    "choices": [
      "n is bound to a copy of each element, so modifying n doesn't touch the original in nums",
      "int is deduced as const here, so the multiplication silently does nothing",
      "range-based for always iterates over a copy of the whole container, not just each element",
      "*= isn't allowed on a range-based for's loop variable, so this wouldn't compile"
    ],
    "answer": 0,
    "explain": "Declaring the loop variable as plain `int` (or any non-reference type) copies each element in; to mutate the real elements you need `int&` instead."
  },
  {
    "q": "After `auto count2 = 5;`, which statement is true?",
    "choices": [
      "count2 is a plain int, exactly as strongly typed as if you'd written `int count2 = 5;`",
      "count2 can later be reassigned to hold a string or double, since auto is dynamically typed",
      "count2's type is only checked at runtime, not at compile time",
      "count2's type stays undecided until the compiler sees how it's used later in the function"
    ],
    "answer": 0,
    "explain": "auto is resolved once, at the declaration, by looking at the initializer - after that the variable is a fixed, ordinary type with no runtime flexibility."
  }
]
```


---

# Functions, Overloading & Default Arguments

In C, a function name is a single, unique thing. You can only ever have one `area`. If you need to compute
area for an `int` rectangle and a `double` rectangle, you write two functions with two names -
`area_int` and `area_double` - because the linker resolves function calls by name alone, and it will
reject two functions sharing one. [C From Zero's phase on functions](/guides/c-from-zero/04-functions-and-program-structure)
covers that world: one name, one signature, forever.

C++ throws that restriction out. **A function name in C++ is not required to be unique - the *signature*
is what has to be unique.** You can write several functions called `area`, as long as each one takes a
different set of parameter types, and the compiler will figure out which one you meant at every call site.
This is called **overloading**, and it's the first genuinely new idea in this guide (phase 2 covered syntax
changes; this is a change in what the *language* lets you express). Understanding *how* the compiler
decides which overload to run - not just that it can - is the actual goal of this phase, because get it
wrong and you'll be staring at call sites wondering why the "obviously correct" function didn't run.

## Overloading: same name, different job

Here's the motivating example, rewritten from the two-function C version into one overloaded C++ name:

```cpp
#include <iostream>

int area(int width, int height) {
    return width * height;
}

double area(double width, double height) {
    return width * height;
}

int main() {
    std::cout << area(3, 4) << "\n";        // calls the int version -> 12
    std::cout << area(3.5, 4.0) << "\n";    // calls the double version -> 14
}
```

Both functions are named `area`. The compiler looks at the *types of the arguments you passed* and picks
the function whose parameter types match best. `area(3, 4)` passes two `int`s, so it calls the `int`
overload. `area(3.5, 4.0)` passes two `double`s, so it calls the `double` overload. You never wrote
`area_int` or `area_double` - one name, two jobs, and the call site reads naturally either way.

📝 **Terminology.** The **signature** of a function is its name plus its parameter types (the return type
does *not* count - you cannot overload two functions that differ only in return type). Overloading means:
same name, different signature. The compiler tells overloads apart internally through **name mangling** -
it encodes the parameter types into the symbol the linker actually sees, so `area(int,int)` and
`area(double,double)` become two distinct linker symbols even though your source code spells them the
same way. That's the mechanism C's linker lacks, and why C can't do this.

## How the compiler picks: overload resolution

When you call an overloaded function, the compiler runs a process called **overload resolution**. For
each candidate function with that name, it checks whether your arguments could work, and ranks how good
the match is. Roughly, from best to worst:

1. **Exact match** - the argument type is already exactly the parameter type (or a trivial reference/const
   adjustment).
2. **Promotion** - a small, "safe" widening, like `char` to `int` or `float` to `double`.
3. **Conversion** - anything else the compiler is willing to do implicitly, like `int` to `double`.

The compiler picks the candidate with the best rank. If two candidates tie for best, or none of them are
viable, you get a compile error instead of a guess - C++ never silently picks a "close enough" overload
when it's genuinely unsure.

```cpp
void show(int x)    { std::cout << "int: " << x << "\n"; }
void show(double x) { std::cout << "double: " << x << "\n"; }

int main() {
    show(5);      // exact match on int -> "int: 5"
    show(5.0);    // exact match on double -> "double: 5"
    show(5.0f);   // float -> promotes to double (no float overload exists) -> "double: 5"
    // show('a');    // char -> promotes to int -> "int: 97" would compile
}
```

⚠️ **The ambiguous call trap.** `show(5.0f)` above resolves cleanly because float-to-double is a
promotion, and a promotion outranks any conversion. But some calls have no single best match. Using the
same two overloads, `show(5L)` - passing a `long` - is ambiguous: `long`-to-`int` and `long`-to-`double`
are both *conversions* of equal rank, so neither overload is better than the other, and the compiler
refuses to guess:

```console
error: call of overloaded 'show(long int)' is ambiguous
```

The fix is never to hope the compiler picks right - it's to either add the exact overload you need
(`show(long)`), or cast explicitly at the call site (`show(static_cast<int>(x))`) so there's only one
possible match. Ambiguity errors are the compiler refusing to gamble with your intent; read them as "you
need to be more specific," not as a bug in the compiler.

💡 **Key point.** Overload resolution looks only at parameter *types*, never at what the function *does*
or what name would read best to a human. Two overloads that do wildly different things but happen to share
a name and similar-looking parameter types is a trap you set for future readers (including future you).
Reserve overloading for functions that do conceptually the *same* thing on different types - like `area`
above, or `std::max(int,int)` and `std::max(double,double)` in the standard library - not for unrelated
operations that happen to want the same verb.

## Default arguments: one function, several call shapes

C++ also lets a function supply a default value for trailing parameters, so callers can omit them:

```cpp
#include <iostream>

void greet(std::string name, std::string greeting = "Hello") {
    std::cout << greeting << ", " << name << "!\n";
}

int main() {
    greet("Ava");                 // "Hello, Ava!"
    greet("Ava", "Good morning"); // "Good morning, Ava!"
}
```

`greeting` has a default, so `greet("Ava")` is really `greet("Ava", "Hello")` with the second argument
filled in for you. This is a lighter-weight alternative to overloading when the "extra" version of a
function isn't a different *type* of parameter, just an optional one - notice we didn't need to write a
second `greet` function that hard-codes `"Hello"`.

Two rules govern default arguments, and both exist to keep a call site unambiguous:

- **Defaults must be trailing.** Once a parameter has a default, every parameter after it must have one
  too. `void f(int a = 1, int b)` does not compile - the compiler couldn't tell, in `f(5)`, whether `5` was
  meant for `a` or `b` if `a` were allowed to be skipped instead.
- **Declare the default once.** If a function has both a declaration (say, in a header) and a definition,
  the default argument goes in the declaration the caller actually sees - usually the header - not in
  both places with (potentially) different values.

```cpp
// header
void log_message(const std::string& text, int level = 1);

// source file - no default repeated here
void log_message(const std::string& text, int level) {
    std::cout << "[" << level << "] " << text << "\n";
}
```

⚠️ **Overloading + defaults can collide.** If you overload `log_message(const std::string&)` *and* give
`log_message(const std::string&, int level = 1)` a default, then `log_message("hi")` becomes ambiguous -
both candidates can satisfy that call with zero extra arguments. Pick one mechanism per situation: use
overloading when the parameter *types* genuinely differ, use a default argument when you just want to make
one trailing parameter optional. Mixing both for the same gap invites exactly this kind of collision.

## How arguments actually get passed

One more piece belongs here before the next phase goes deep on it: by default, C++ function parameters are
**passed by value**, exactly like C - the function gets its own copy, and changes inside the function don't
touch the caller's variable. That's unchanged from C. What *is* new is that C++ also gives you **references**
(`std::string&` instead of `std::string`) as a cleaner alternative to C's pointer-based "pass a pointer so the
function can modify the original." You saw a reference parameter above (`const std::string& text`) without
it being explained - that's deliberate; [Phase 5: References vs Pointers](05-references-vs-pointers.md) is
entirely about what that `&` means and why C++ programmers reach for it constantly.

## Recap

1. **Overloading:** several functions can share a name in C++ as long as their parameter types (their
   *signature*) differ - impossible in C, where the linker needs one name per function.
2. **Overload resolution** ranks candidates by how well argument types match: exact match beats promotion
   beats conversion; ties or no viable match are compile errors, not guesses.
3. **Ambiguous calls** happen when two overloads become equally good matches - fix them by adding the exact
   overload needed or casting explicitly at the call site, never by hoping the compiler picks right.
4. **Default arguments** let trailing parameters be optional (`greeting = "Hello"`); defaults must be
   trailing, and are declared once, in the declaration the caller sees.
5. Don't mix overloading and default arguments to cover the same gap in parameter count - it's a classic
   way to make a call site ambiguous.
6. Parameters are still passed by value like C by default; C++'s `&` reference parameters are the modern
   alternative to C's pointer-passing trick, covered fully next phase.

## Quick check

Test yourself on the idea that makes this phase click - what actually makes two overloads distinct, and how the compiler breaks ties:

```quiz
[
  {
    "q": "You write `int area(int w, int h)` and `double area(int w, int h)` - same parameters, different return type. What happens?",
    "choices": [
      "It compiles fine - the compiler picks whichever version the caller assigns the result to",
      "It fails to compile - return type alone doesn't make a valid overload, only the parameter types (the signature) do",
      "It compiles, but only the `int` version is ever callable"
    ],
    "answer": 1,
    "explain": "The signature is name plus parameter types; return type isn't part of it, so two functions differing only in return type are a duplicate definition, not an overload."
  },
  {
    "q": "Given `void show(int x)` and `void show(double x)`, what does `show(5.0f)` call?",
    "choices": [
      "show(int) - the float gets truncated to fit",
      "show(double) - float to double is a promotion, which ranks better than a conversion to int",
      "Neither - a plain float argument is always a compile error"
    ],
    "answer": 1,
    "explain": "float to double is a promotion (safe widening), which beats the conversion float would need to become int, so overload resolution picks show(double)."
  },
  {
    "q": "Which of these default-argument declarations fails to compile?",
    "choices": [
      "void f(int a, int b = 1)",
      "void f(int a = 1, int b)",
      "void f(int a = 1, int b = 2)"
    ],
    "answer": 1,
    "explain": "Defaults must be trailing - once a parameter has a default, every parameter after it needs one too, otherwise a call like f(5) couldn't tell if 5 was meant for a or b."
  }
]
```


---

# References vs Pointers

If you came from [C From Zero](/guides/c-from-zero) or any C background, you already know pointers: a variable that holds an address, which you dereference with `*` to get at the thing it points to. C++ keeps pointers exactly as they were, but it adds a second way to refer to an existing object: the **reference**. This phase is about understanding what a reference actually is, why it exists, and - the question that trips up almost everyone at first - when to reach for which one.

## The mental model: an alias, not a variable

**What a reference actually is.** A reference is not a new object that stores an address. It is *another name for an object that already exists*. Once you write `int& r = x;`, `r` and `x` are the same variable wearing two name tags. There is no "reference object" sitting in memory that you could inspect separately from `x` - the compiler just makes `r` compile down to uses of `x` (usually implemented as a pointer under the hood, but that's an implementation detail, not part of the model).

Compare that to a pointer, which genuinely is its own variable:

```cpp
int x = 10;

int* p = &x;   // p is a variable. It holds the address of x.
int& r = x;    // r is not a variable of its own. It IS x, under another name.

*p = 20;       // "go to the address p holds, and change what's there"
r = 30;        // just... assign to r. It's x.

std::cout << x << " " << *p << " " << r << "\n";  // 30 30 30
```
```console
30 30 30
```
*What just happened:* `p` and `r` both end up letting you read and write `x`, but the *mechanism* is different. `p` is a separate variable you dereference to reach `x`. `r` has no separate identity - assigning to `r` is, semantically, assigning to `x`. That difference is the source of every rule in this phase.

## Three rules that follow directly from "a reference is an alias"

Because a reference is another name for something, not a value that can point at different things over time, three consequences fall straight out of the definition:

1. **A reference must be bound at declaration.** You can't declare `int& r;` and bind it later - there's no such thing as an unbound alias. This is a compile error, not a runtime concern.
2. **A reference can never be null.** A pointer can hold `nullptr` - "I point at nothing." A reference always refers to a real object, because it *is* that object's other name. (You can technically construct a "null reference" through undefined behavior by dereferencing a null pointer and binding a reference to the result, but that's a bug, not a feature - never rely on it.)
3. **A reference can't be reseated.** `r = y;` doesn't make `r` refer to `y` - it assigns `y`'s value into whatever `r` already refers to (`x`, in the example above). Once bound, always bound to the same object.

Pointers have none of these restrictions, and that's exactly why they still exist in C++ instead of references replacing them outright:

```cpp
int a = 1, b = 2;

int* p = &a;
p = &b;        // fine: p now points at b instead
p = nullptr;   // fine: p points at nothing

int& r = a;
// r = b;      // NOT a reseat - this would set a = b (a becomes 2)
// int& r2;    // error: reference must be initialized
```

So the two tools have opposite personalities: a pointer is a *flexible, optional, reseatable* handle to something (or nothing). A reference is a *fixed, mandatory, permanent* alias.

## Why this exists: references are pointers with the footguns removed

Pointers in C give you total freedom, and total freedom is exactly how you get null-pointer crashes, dangling-pointer corruption, and code where you can never tell just by reading a function signature whether a pointer parameter is allowed to be null. C++ added references so that the *extremely common* case - "let this function operate on an existing object, without copying it, and I promise there's always a real object there" - can be expressed in a way the compiler enforces for you. You don't check `if (r != nullptr)` for a reference, because that check is a category error: it can't be null, full stop.

That's the real payoff. It's not that references are "safer pointers" in some vague sense - it's that an entire class of bugs (null dereference, forgetting to check, unioning "no object" with "a valid object" in one type) is *ruled out by the type itself*.

## The default for function parameters: pass by const reference

Here's where this phase earns its keep day-to-day. When you write a function that takes a large object and only needs to *read* it, passing by value copies the whole thing:

```cpp
#include <string>

void greet_by_value(std::string name) {     // copies the whole string
    std::cout << "Hello, " << name << "\n";
}

void greet_by_ref(const std::string& name) { // no copy - name is an alias
    std::cout << "Hello, " << name << "\n";
}
```

`greet_by_ref` takes a `const std::string&`: a reference (no copy, cheap) that's `const` (the function promises not to modify the caller's object). This is the default you reach for whenever a parameter is "read this object" and the object isn't a tiny, cheap-to-copy type like `int` or `double`. For those small types, just pass by value - copying an `int` is exactly as cheap as passing a reference to it, and simpler to read.

If the function *needs* to modify the caller's object, drop the `const`:

```cpp
void double_it(int& n) {   // non-const reference: can modify the caller's variable
    n *= 2;
}

int main() {
    int x = 21;
    double_it(x);
    std::cout << x << "\n";   // 42
}
```
```console
42
```
*What just happened:* no `&x` at the call site, no `*` inside the function - `n` is just another name for `x` for the duration of the call. This reads cleanly *and* the signature `int& n` documents, right there in the header, that this function is allowed to change what you pass it. That's a real advantage over the pointer version, `void double_it(int* n)`, where a reader can't tell from the call site `double_it(&x)` whether the function might leave `x` unchanged (read-only) or mutate it - the pointer syntax makes both look the same.

## When you actually still need a pointer

References cover "always refers to something, never needs to change what it refers to" - which is most parameter-passing. Reach for a pointer instead when you genuinely need one of the things references can't do:

- **Optionality.** The value might legitimately not exist yet. `Node* next;` in a linked list needs to be `nullptr` sometimes - there's no "empty reference" to express that.
- **Reseating.** You need the same variable to point at different objects over its lifetime (a loop cursor walking a list, a pointer reassigned as you search).
- **Arrays and low-level buffer work.** Pointer arithmetic (`p + 1`, `p[i]`) is a pointer thing; references don't support it.
- **Storing "maybe references" in a container.** You can't have a `std::vector<int&>` - references aren't reseatable, so most containers, which need to reassign elements internally, can't hold them. Store pointers (or, in modern C++, a `std::reference_wrapper`-style tool you'll meet later) instead.

A decision that becomes automatic with practice:

```mermaid
flowchart TD
  Q{Does the thing you're<br/>referring to always exist?}
  Q -- No, might be absent --> P[Use a pointer,<br/>nullptr means absent]
  Q -- Yes, always a real object --> Q2{Do you need to make it<br/>refer to something else later?}
  Q2 -- Yes --> P
  Q2 -- No --> R[Use a reference]
```

## Reading declarations left to right

One last practical habit: when you see `T&` or `T*` in code, read it as part of the *type*, not as an operator glued to the variable name. `int& r = x;` declares `r` to have type "reference to `int`." The common style of writing `int &r` (space before the name) is legal but misleading for exactly this reason - it makes it look like `&` belongs to `r` rather than to `int`. Prefer `int& r` and it'll read the way the compiler actually parses it.

One gotcha worth flagging early, because it bites everyone once: never return a reference (or a pointer) to a local variable.

```cpp
int& broken() {
    int local = 5;
    return local;   // local's storage is gone the instant the function returns
}   // compiler warning: reference to local variable returned
```

`local` lives on the stack frame of `broken()`, and that frame is destroyed the moment the function returns. The reference you handed back is now an alias for memory that no longer belongs to anything - a *dangling reference*. Using it is undefined behavior: it might print the old value, garbage, or crash, and it might do a different one of those each time you run it. Rule of thumb: only return a reference to something that outlives the function call - a member of the object, a parameter that was itself passed by reference, or a global. Otherwise, return by value.

## Recap

1. A **pointer** is a variable holding an address: it can be null, reseated, and used in arithmetic.
2. A **reference** is another name for an existing object: it must be bound at declaration, can never be null, and can never be reseated.
3. Default function parameters to **`const T&`** for anything non-trivial to copy; drop `const` when the function needs to mutate the caller's object; pass small types (`int`, `double`, ...) by value.
4. Reach for a **pointer** when you need optionality (`nullptr`), reseating, pointer arithmetic, or storage in most containers.
5. Never return a reference or pointer to a local variable - its storage is gone the instant the function returns.

References are the first piece of C++'s bigger theme: giving you safer, more expressive defaults on top of what C already let you do with raw addresses. The next phase puts that same idea to real use, building your own types with **classes and objects**.

### Check yourself

```quiz
[
  {
    "q": "Given `int x = 1, y = 2; int& r = x; r = y;` - what happens?",
    "choices": ["r now refers to y instead of x", "x is set to 2, and r still refers to x", "Compile error: references can't be reassigned"],
    "answer": 1,
    "explain": "r = y is not a reseat - it assigns y's value into whatever r already refers to, which is x."
  },
  {
    "q": "Why prefer `void greet(const std::string& name)` over `void greet(std::string name)` when the function only reads a large object?",
    "choices": ["It avoids copying the object while still stopping the function from modifying it", "It lets name be null if the caller has nothing to pass", "It lets the function reseat name to a different string later"],
    "answer": 0,
    "explain": "A const reference is a no-copy alias, and const blocks the function from writing through it."
  },
  {
    "q": "`int& broken() { int local = 5; return local; }` - what's wrong here?",
    "choices": ["Nothing - returning a reference avoids copying the int", "It returns a reference to local's storage, which is destroyed the instant the function returns", "It should be rewritten to return a pointer instead, since references can't be returned"],
    "answer": 1,
    "explain": "local lives on broken()'s stack frame, so the reference dangles the moment the function ends."
  }
]
```


---

# Classes & Objects

Phase 2 showed you what changed going from C to C++: references, `bool`, better strings, `new`/`delete`.
Those were surface changes. This phase is the real one. **The class is the thing C++ is actually built
around.** Once you can write and reason about classes, phases 7 through 14 - RAII, copying, operators,
templates, the STL, smart pointers, inheritance - are all just answers to questions that classes raise.
Get the mental model right here and the rest of the language stops feeling like a pile of features and
starts feeling like one coherent idea, worked out in different directions.

## What a class actually is

**What it actually is.** A class is a blueprint for a type you design yourself. It bundles two things that
C keeps apart: the *data* that describes something, and the *functions* that are allowed to act on that
data. An `int` is a type the language gives you. A class is a type *you* give the language.

If you've read [C From Zero: Structs & Typedef](/guides/c-from-zero/07-structs-and-typedef), you already
know half of this. A C `struct` groups data:

```c
struct Account {
    double balance;
};
```

But in C, nothing ties `deposit(&acc, 50.0)` to that struct. It's just a function that happens to take an
`Account*` - nothing stops you from calling it on the wrong struct, forgetting to call it at all, or
poking `acc.balance` directly from anywhere in the program. The struct doesn't *own* its behavior.

A C++ class fixes that by putting the functions **inside** the type, and letting the type say which parts
of itself the outside world is even allowed to touch:

```cpp
class Account {
public:
    void deposit(double amount) {
        balance += amount;
    }
    double getBalance() const {
        return balance;
    }
private:
    double balance = 0.0;
};
```

```cpp
#include <iostream>

int main() {
    Account acc;
    acc.deposit(50.0);
    acc.deposit(25.0);
    std::cout << acc.getBalance() << "\n";   // 75
}
```
```console
$ g++ -std=c++17 account.cpp -o account && ./account
75
```
*What just happened:* `acc` is an **object** - a concrete instance of the `Account` class, with its own
`balance`. `deposit` and `getBalance` are **member functions**: functions that live inside the class and
act on one particular object's data. Calling `acc.deposit(50.0)` runs `deposit` with `acc`'s data in
scope. There is no way to reach into `acc` and set `balance` directly from `main` - the class itself
forbids it. That's the whole idea, stated in one example.

## Classes vs. structs: one real difference

C++ kept the `struct` keyword and quietly turned it into a class in disguise. In C++, `struct` and `class`
are **the same feature**, separated by a single default: `struct` defaults to `public`, `class` defaults
to `private`. That one default shows up in two places - member access, and (later, when you get to
inheritance in phase 14) which base classes a derived type inherits:

| | `struct` | `class` |
|---|---|---|
| Default member access | `public` | `private` |
| Default inheritance access | `public` | `private` |
| Everything else | identical | identical |

```cpp
struct Point { double x, y; };   // x and y are public by default

class Point2 { double x, y; };   // x and y are private by default
```

📝 **Terminology.** *Member* means "something declared inside the class" - a **data member** (a variable)
or a **member function** (a function, sometimes called a *method* in other languages, though C++ mostly
just says "member function").

Convention, not the compiler, decides which keyword you reach for: use `struct` for a type that's really
just a passive bundle of public data (a `Point`, a `Color`, a config block), and `class` for a type with
invariants to protect and behavior to hide. Nothing stops you from writing `struct` with private members
and member functions - it works identically - but doing so surprises every C++ programmer who reads it.
Match the keyword to the intent.

## Access control: why "private" is a feature, not a restriction

**Why this exists.** `balance` is `private` in the first example on purpose. Imagine `Account` also
tracked a transaction count that must always match the number of deposits. If any code anywhere could
write `acc.balance = -500;` directly, nothing could guarantee that invariant held. By making `balance`
private and forcing every change through `deposit` (and, later, a `withdraw` you'd write the same way),
the class becomes the *only* code that can break its own rules - and you only have to check `deposit` and
`withdraw` for bugs, not every line in the program that ever touched an `Account`.

This is **encapsulation**: hiding the data, exposing a small, deliberate set of operations on it. It's the
same instinct as a library's `.h` file only declaring the functions callers need (phase 8 revisits this
for headers) - except now the hiding happens *per object*, enforced by the compiler, not by convention.

There are three access levels:

```cpp
class Widget {
public:
    // callable from anywhere - the class's public interface
protected:
    // callable from this class and classes derived from it (phase 14)
private:
    // callable only from inside this class's own member functions
};
```

💡 **Key point.** Access control is checked at compile time, per *class*, not per *object*. One
`Account`'s member function can freely read another `Account`'s private `balance`, because both are
instances of the same class. Privacy in C++ means "hidden from outside code," not "hidden from other
objects of the same type."

## `this`: how a member function knows which object it's operating on

**What it actually is.** Every non-static member function secretly receives a pointer to the object it
was called on, named `this`. When `deposit` writes `balance += amount;`, it's really shorthand for
`this->balance += amount;`. You rarely need to write `this` explicitly, but two situations call for it:

```cpp
class Account {
public:
    void setBalance(double balance) {
        this->balance = balance;   // parameter shadows the member; this-> disambiguates
    }
    Account& deposit(double amount) {
        this->balance += amount;
        return *this;              // return the object itself, to allow chaining
    }
private:
    double balance = 0.0;
};
```

```cpp
Account acc;
acc.deposit(10).deposit(20).deposit(30);   // chained: each call returns *this
```

*What just happened:* `setBalance`'s parameter is also named `balance`, so plain `balance = balance;`
would just assign the parameter to itself. `this->balance` reaches past the parameter to the member.
Separately, `deposit` returns `*this` (dereferencing the pointer to get the object back by reference), so
the caller can chain calls: each `.deposit(...)` runs, hands the same object back, and the next call runs
on it immediately.

⚠️ **The naming trap.** New C++ programmers often avoid the parameter-shadows-member collision by giving
members ugly names like `m_balance` or `balance_`. Either style is fine and common in real codebases - the
point isn't which convention you pick, it's that you pick one on purpose rather than fighting `this`
every time a parameter and a member want the same name.

## Defining member functions outside the class

Writing every function body inside the class works, but for anything longer than a line or two, C++
programmers usually **declare** the function in the class and **define** it below using the scope
resolution operator `::`, which reads as "belongs to":

```cpp
class Account {
public:
    void deposit(double amount);      // declaration only
    double getBalance() const;
private:
    double balance = 0.0;
};

void Account::deposit(double amount) {   // "deposit, which belongs to Account"
    balance += amount;
}

double Account::getBalance() const {
    return balance;
}
```

This is the same declaration/definition split you already know from ordinary functions (phase 4) and from
C header files - it just needs `Account::` to say which class's `deposit` this is. Real projects put the
class declaration in a `.h` file and these definitions in a matching `.cpp` file, exactly like phase 8
will formalize for headers in general.

🪖 **War story.** A common first mistake is marking a read-only member function like `getBalance` without
`const`, then being unable to call it on a `const Account&` parameter later - the compiler simply refuses,
because a non-`const` member function is allowed to modify the object, and a `const` object can't be
handed to anything that might modify it. The fix in the example above, `double getBalance() const;`, tells
the compiler "I promise not to touch this object's data" - and unlocks calling it wherever the object is
`const`. Get in the habit of marking every member function `const` unless it genuinely needs to change
something; it costs nothing and the compiler starts catching bugs for you.

## Objects have a lifecycle you don't fully control yet

You may have noticed `Account acc;` created a working object with `balance` already at `0.0`, with no
explicit setup call. Something initialized it - and something will eventually clean it up when `acc` goes
out of scope. That "something" is a **constructor** and a **destructor**, and they are the single most
important idea in C++'s object model: the language guarantees code runs automatically when an object is
born and when it dies. That guarantee is called **RAII**, and it's the subject of the next phase - so
consider everything in this phase the stage-setting for the real payoff.

## Recap

1. **A class bundles data with the functions allowed to touch it** - unlike a C `struct`, the behavior is
   part of the type, not a loose function that happens to take a pointer to it.
2. **`struct` and `class` are the same mechanism**; the only difference is the default access level
   (`public` vs `private`). Use `struct` for plain data, `class` for anything with invariants to protect.
3. **Access control (`public`/`protected`/`private`) enforces encapsulation** - the class decides what
   outside code can touch, so it can guarantee its own rules stay true.
4. **`this`** is the hidden pointer every member function gets to the object it was called on; use
   `this->` to resolve a name collision, and return `*this` to enable chaining.
5. **Member functions can be declared in the class and defined outside it** with `Class::function`, the
   same declaration/definition split as ordinary functions.
6. **`const` on a member function** promises it won't modify the object - mark read-only member functions
   `const` by default.
7. Objects already have data ready the moment they exist, and clean up automatically when they leave
   scope. *Why* is the next phase's entire subject: constructors, destructors, and RAII.

## Quick check

Test yourself on the ideas that matter most this phase - what actually separates `class` from `struct`, and how access control works:

```quiz
[
  {
    "q": "What is the real difference between `struct` and `class` in C++?",
    "choices": [
      "`struct` defaults to `public`, `class` defaults to `private` - the same flip governs both member access and default inheritance access",
      "`struct` cannot have member functions, only `class` can",
      "`struct` has more runtime overhead than `class`",
      "`class` supports inheritance, `struct` does not"
    ],
    "answer": 0,
    "explain": "struct and class are the same mechanism; the only thing that differs is the public-vs-private default, which applies both to members and to how base classes are inherited. Both can have member functions, access control, and inheritance."
  },
  {
    "q": "One `Account` object's member function tries to read another `Account` object's private `balance`. Is this legal?",
    "choices": [
      "Yes - access control is checked per class, not per object, so any Account's member function can read another Account's private data",
      "No - private members are only visible to the exact object that owns them",
      "Only if the two objects were created in the same function",
      "Only if `balance` is marked `friend`"
    ],
    "answer": 0,
    "explain": "Access control is enforced at compile time per class: private hides data from outside code, not from other instances of the same class."
  },
  {
    "q": "In `void setBalance(double balance) { balance = balance; }`, the member never actually changes. Why, and what fixes it?",
    "choices": [
      "The parameter `balance` shadows the member `balance`; writing `this->balance = balance;` reaches past the parameter to the member",
      "C++ requires `self` instead of `this` for member access",
      "A parameter can never share a name with a member - one of them must be renamed",
      "It works correctly as written; the member is updated"
    ],
    "answer": 0,
    "explain": "Inside the function body, the parameter's name hides the member's name; this-> explicitly targets the object's own data instead of the parameter."
  }
]
```


---

# Constructors, Destructors & RAII

In [Phase 6](06-classes-and-objects.md) you gave objects data and behavior. This phase gives them a
birth and a death - code that runs automatically the moment an object comes into being, and code that
runs automatically the moment it's gone. That second half, the automatic death, turns out to be the
single most important idea in C++. It has a name - **RAII** - and once it clicks, it explains half of
why C++ code looks the way it does.

## The mental model: objects have a lifecycle

Every object in C++ goes through the same three moments, whether it's a local variable, a class member,
or something on the heap:

1. **Construction** - memory for the object exists, and now its constructor runs to set it up.
2. **Life** - you use it.
3. **Destruction** - the object's destructor runs, then the memory is reclaimed.

You've already seen construction and destruction happen for built-in things without thinking about it:
a `std::vector` allocates its buffer when you create it and frees that buffer the instant it goes out of
scope. That's not magic the language special-cased for `vector`. It's a constructor and a destructor,
and you can write the exact same behavior into your own types.

**What makes this different from C:** in C, `struct Buffer { char *data; }` is just a data layout. Nothing
runs when it's created, and nothing runs when it goes away - if `data` was `malloc`'d, *you* remember to
`free` it, on every path out of the function, including the early `return` and the branch you added six
months later and forgot about. C++ lets a type own that responsibility itself.

## Constructors: setting up

A **constructor** is a special member function with the same name as the class and no return type. It
runs automatically whenever an object is created.

```cpp
class Point {
public:
    Point(double x, double y) : x_(x), y_(y) {
        std::cout << "Point constructed at (" << x_ << ", " << y_ << ")\n";
    }

private:
    double x_;
    double y_;
};

int main() {
    Point p(3.0, 4.0);   // constructor runs here, automatically
}
```
```console
Point constructed at (3, 4)
```

Notice `: x_(x), y_(y)` before the constructor's `{ }` body. That's the **member initializer list**, and
it's not just style - it's *how members get their first value*. Without it, `x_` and `y_` would be left
with indeterminate values first and then you'd overwrite them by assignment inside the body. For a
primitive `double` that's merely redundant, but for a member like `std::string` it's genuinely wasteful:
the member gets fully default-constructed and *then* reassigned - two steps where the list does one.
Prefer the initializer list; use the body only for logic that isn't "just set this member."

💡 **Key point.** A class can have several constructors that differ by parameters - this is just
[overloading](04-functions-overloading-and-default-arguments.md) applied to construction. A constructor
with no parameters is the **default constructor**; it's what runs for `Point p;` with nothing in the
parentheses, if you provide one (or if the compiler can generate one for you - more on that in
[Phase 8](08-copy-move-and-the-rule-of-five.md)).

⚠️ **The `explicit` trap.** A constructor taking exactly one argument doubles as an implicit conversion
unless you stop it:

```cpp
class Meters {
public:
    Meters(double value) : value_(value) {}
private:
    double value_;
};

void print(Meters m);

print(5.0);   // compiles! 5.0 silently becomes a Meters
```

That silent conversion is rarely what you want - it lets a plain `double` sneak into an API expecting a
`Meters` with no visible sign it happened. Mark single-argument constructors `explicit` unless you
*specifically* want the implicit conversion:

```cpp
explicit Meters(double value) : value_(value) {}

print(5.0);          // error now: no implicit conversion
print(Meters(5.0));  // fine: you said it on purpose
```

## Destructors: tearing down

A **destructor** is `~ClassName()` - no parameters, no return type, and a class has at most one. It runs
automatically the instant the object's lifetime ends: when a local variable's scope closes, when a
member's owning object is destroyed, or when you `delete` a heap object.

```cpp
class Point {
public:
    Point(double x, double y) : x_(x), y_(y) {}
    ~Point() {
        std::cout << "Point destroyed\n";
    }
private:
    double x_, y_;
};

void demo() {
    Point p(1.0, 2.0);
    std::cout << "using p\n";
}   // p goes out of scope right here

int main() {
    demo();
}
```
```console
using p
Point destroyed
```

No call to destroy `p` anywhere in the code. The compiler inserted it at the closing `}` of `demo`,
because that's where `p`'s scope ends. This is **deterministic destruction**: you can point at the exact
line where cleanup happens, before the program even runs. Compare that to a garbage-collected language,
where an object becomes eligible for collection at that point but the actual cleanup happens whenever the
collector next runs, on its own schedule, possibly much later. C++ gives you the same "when it's dead,
it's cleaned up" guarantee, minus the "eventually."

📝 **Terminology.** People often say an object's destructor runs "when it goes out of scope." More
precisely: it runs when the object's **lifetime ends**, which for a local variable is scope exit, but
also happens for heap objects on `delete`, for members when their owning object is destroyed, and
(critically) during **stack unwinding** - when an exception is thrown, every local object between the
`throw` and the matching `catch` gets its destructor run, in order, on the way out. You'll see this again
in [Phase 15](15-error-handling-exceptions-and-alternatives.md).

## RAII: the idea destructors make possible

Here's the leap. If a destructor is *guaranteed* to run when an object dies, and it runs *no matter which
path* the code takes out of scope (normal return, early return, break, or an exception unwinding
through), then a destructor is the perfect place to release anything the object is holding onto: heap
memory, a file handle, a mutex lock, a network socket. This pattern has a name:

**RAII - Resource Acquisition Is Initialization.** Acquire a resource in the constructor. Release it in
the destructor. Tie the resource's lifetime to an object's lifetime, and let the compiler's automatic
destruction do the releasing for you.

Watch the difference on a file handle. First, the C way:

```c
void process_c(const char *path) {
    FILE *f = fopen(path, "r");
    if (!f) return;

    if (something_goes_wrong()) {
        return;              // leak: f is never closed
    }

    // ... use f ...
    fclose(f);
}
```

Every early exit is a place a fix could be forgotten - and in a function with five exit paths, someone
eventually will. Now the RAII way:

```cpp
class FileHandle {
public:
    explicit FileHandle(const char *path) : f_(std::fopen(path, "r")) {}
    ~FileHandle() {
        if (f_) std::fclose(f_);
    }
    FILE *get() const { return f_; }

private:
    FILE *f_;
};

void process_cpp(const char *path) {
    FileHandle f(path);
    if (!f.get()) return;

    if (something_goes_wrong()) {
        return;               // f_'s destructor still runs. No leak.
    }

    // ... use f.get() ...
}   // and here, on the normal path, same guarantee.
```

`FileHandle`'s destructor runs on *every* exit from `process_cpp` - the early return, an exception, or
falling off the end - because the compiler put the cleanup at the language level, not at each call site.
You didn't write three copies of "remember to close the file." You wrote one destructor, once, and it
covers every path forever, including paths someone adds next year.

This is why `std::vector`, `std::string`, and `std::unique_ptr` (coming in
[Phase 13](13-smart-pointers-and-modern-memory-management.md)) never need a manual `free` or `delete`:
they're all RAII wrappers around a resource, exactly like `FileHandle` above, just written once by the
standard library so nobody has to write it twice.

## Construction and destruction order

When a class has several members, they're constructed in the order they're **declared** in the class -
not the order they appear in the initializer list - and destroyed in the exact reverse order:

```cpp
class Widget {
public:
    Widget() : a_(1), b_(2) {}   // a_ built first, then b_ (declaration order)
private:
    A a_;
    B b_;
};   // ~Widget() destroys b_ first, then a_
```

⚠️ **Gotcha.** If your initializer list writes `b_(2), a_(1)` - out of declaration order - most compilers
will warn you (`-Wreorder`). The list's *order in the source* doesn't control construction order; the
class's member declaration order does. Always write the initializer list in declaration order so the
code reads the way it runs.

🪖 **War story.** A common bug: a member holds a raw pointer *into* another member declared later in the
class, and the constructor tries to use it. Because members build top-to-bottom, that later member
doesn't exist yet when the earlier one's constructor runs - the pointer is dangling from the start. The
fix is almost always to reorder the member declarations, not to fight the initializer list.

## Recap

1. Every object has a lifecycle: **construction** (constructor runs), **life**, **destruction**
   (destructor runs) - and in C++ both are automatic and deterministic.
2. **Constructors** set up an object; prefer the **member initializer list** over assignment in the body,
   and mark single-argument constructors `explicit` to block silent conversions.
3. **Destructors** (`~ClassName()`) run the instant an object's lifetime ends - scope exit, `delete`, or
   exception unwinding - on every path, guaranteed.
4. **RAII** ties a resource's lifetime to an object's lifetime: acquire in the constructor, release in
   the destructor. That single pattern is *why* C++ can manage memory, files, and locks without a garbage
   collector and without manual cleanup calls scattered through the code.
5. Members are constructed in **declaration order** and destroyed in the **reverse** of that order,
   regardless of the initializer list's written order.

RAII answers "how does cleanup happen automatically." It doesn't yet answer what happens when you *copy*
an RAII object - two owners now think they're responsible for freeing the same resource. That collision,
and the five special functions that resolve it, is the crux of the whole language.

## Quick check

Test yourself on the idea that makes this phase matter - that RAII ties cleanup to an object's
lifetime instead of leaving it to a programmer's memory:

```quiz
[
  {
    "q": "What does RAII actually guarantee?",
    "choices": [
      "A resource acquired in a constructor gets released in the destructor, automatically, on every exit path",
      "The compiler runs a garbage collector periodically to free unused objects",
      "Memory is freed the moment the last variable pointing to it is reassigned",
      "Resources are released only if the program exits normally, without an exception"
    ],
    "answer": 0,
    "explain": "RAII ties a resource's lifetime to an object's lifetime: the constructor acquires it, the destructor releases it, and the destructor is guaranteed to run on every path out of scope - normal return, early return, or exception unwinding."
  },
  {
    "q": "In `FileHandle f(path); if (!f.get()) return;`, why doesn't the early `return` leak the file handle the way the C version does?",
    "choices": [
      "The compiler runs `~FileHandle()` at that `return`, because that's where `f`'s lifetime ends, regardless of which exit path was taken",
      "The `return` statement automatically calls `fclose` on any open file handles it detects",
      "`FileHandle` doesn't actually leak in C either, the C example was just missing an `if`",
      "It only avoids the leak because `f.get()` happened to return null"
    ],
    "answer": 0,
    "explain": "Destruction is tied to lifetime, not to how a function exits. Every path out of `process_cpp` - including the early `return` - ends `f`'s lifetime at that point, so the compiler inserts the destructor call there too."
  },
  {
    "q": "A class has members `A a_;` then `B b_;`, and the constructor's initializer list writes `b_(2), a_(1)`. What order do construction and destruction actually happen in?",
    "choices": [
      "`a_` constructed first, then `b_`; destroyed in reverse - `b_` then `a_` - regardless of the initializer list's order",
      "`b_` constructed first because it's listed first in the initializer list, then `a_`",
      "Both are constructed in whatever order the compiler finds fastest",
      "Construction order follows the initializer list, but destruction order is unrelated to either"
    ],
    "answer": 0,
    "explain": "Declaration order controls construction order, not the order written in the initializer list - `a_` is declared first so it's built first, and destruction always reverses that: `b_` then `a_`."
  }
]
```


---

# Copy, Move & the Rule of Five

This is the phase the whole book has been building toward. Phase 7 gave you RAII: a constructor acquires a resource, a destructor releases it, and scope does the rest. That idea is beautiful right up until you copy the object - and then, if you haven't told C++ what copying *means* for your class, it quietly does the wrong thing. Understanding exactly what "wrong" looks like, and how to fix it, is the single most important skill in C++. Get this phase and the rest of the language falls into place. Skip it and you'll spend years chasing crashes that only show up in release builds.

## The mental model: every type answers five questions

In C++, objects are values by default (unlike C#, Java, or Python, where `Widget b = a` copies a reference - see phase 2 for that contrast). When you write `Widget b = a`, C++ has to *build a new object* that behaves like `a`. To do that, every type - whether you wrote it or the compiler did - answers five questions:

1. How do I **destroy** you? (destructor)
2. How do I **copy** you from an existing object? (copy constructor)
3. How do I **copy** you into an already-existing object? (copy assignment)
4. How do I **steal** your guts instead of copying them, because you're about to be thrown away anyway? (move constructor)
5. How do I steal your guts into an already-existing object? (move assignment)

If you write none of these, the compiler generates all five for you, member by member: destroy each member, copy each member, move each member. For a class made only of plain values (`int`, `double`) or well-behaved RAII types (`std::string`, `std::vector`), that generated behavior is *exactly right* and you never think about it again. The trouble starts the moment your class manages a resource by hand - a raw pointer, a file handle, anything phase 7 taught you to wrap in a constructor/destructor pair.

## Watch the compiler get it wrong

Take the `Buffer` class from phase 7, a thin RAII wrapper around a heap array:

```cpp
class Buffer {
public:
    Buffer(size_t size) : size_(size), data_(new int[size]) {}
    ~Buffer() { delete[] data_; }

    int size() const { return size_; }
    int* data() { return data_; }

private:
    size_t size_;
    int* data_;
};
```

We never wrote a copy constructor, so the compiler generated one: it copies `size_` (fine, it's an `int`) and copies `data_` (a *pointer* - so it copies the address, not the array it points to). That's called a **shallow copy**, and it's a landmine:

```cpp
Buffer a(10);
Buffer b = a;   // compiler-generated copy: b.data_ == a.data_ !

// a and b now both "own" the same heap array.
```

Both objects think they own that array. When `a` goes out of scope, its destructor runs `delete[] data_`. When `b` then goes out of scope, its destructor runs `delete[] data_` on the *same already-freed pointer* - a double free, which is undefined behavior (phase 17 covers UB in depth; for now, treat it as "your program is no longer trustworthy"). Worse, any use of `b` after `a` is destroyed is a use of freed memory. Neither of these crashes reliably or immediately, which is exactly why it's dangerous: it can pass every test you write and then corrupt memory in production.

The fix isn't "be more careful." It's: **tell the compiler what copying actually means for this type.**

## Writing the copy operations

```cpp
class Buffer {
public:
    Buffer(size_t size) : size_(size), data_(new int[size]) {}
    ~Buffer() { delete[] data_; }

    // Copy constructor: build a *new* Buffer from an existing one.
    Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {
        std::copy(other.data_, other.data_ + size_, data_);
    }

    // Copy assignment: *this already exists; make it equal to other.
    Buffer& operator=(const Buffer& other) {
        if (this == &other) return *this;      // guard self-assignment
        int* new_data = new int[other.size_];  // allocate first
        std::copy(other.data_, other.data_ + other.size_, new_data);
        delete[] data_;                        // then release the old data
        data_ = new_data;
        size_ = other.size_;
        return *this;
    }

    size_t size_;
    int* data_;
};
```

Now `Buffer b = a;` allocates its own array and copies the *values*. Two independent objects, two independent arrays, no double free. Notice the "allocate first, then release" order in copy assignment - if `new int[]` throws (out of memory), `*this` is left untouched instead of half-destroyed. That's the same "acquire before you release" discipline RAII taught you in phase 7.

## Move: skip the copy when the source is disposable

Copying is correct, but for a large `Buffer`, it's wasteful in one common case: when the source object is a temporary that's about to be destroyed anyway. Consider `Buffer make_buffer() { Buffer b(1000000); return b; }`. Copying a million ints just to immediately destroy the original would be pure waste - the data didn't need to be duplicated, just handed off. (For this exact named return the compiler usually skips the handoff entirely via *copy elision* / NRVO; the move constructor is what keeps that handoff cheap everywhere elision doesn't reach, like dropping a temporary into a `std::vector`.)

C++11 added **rvalue references** (`Buffer&&`) to name exactly this situation: a reference that only binds to values the compiler knows are temporary (or that you explicitly mark as "safe to plunder" with `std::move`). A move constructor and move assignment operator take a `Buffer&&` and, instead of copying, **steal the pointer** and leave the source in a harmless empty state:

```cpp
    // Move constructor: cannibalize other's insides, leave it empty.
    Buffer(Buffer&& other) noexcept
        : size_(other.size_), data_(other.data_) {
        other.data_ = nullptr;   // other's destructor must not free this now
        other.size_ = 0;
    }

    // Move assignment: release our own data, then steal other's.
    Buffer& operator=(Buffer&& other) noexcept {
        if (this == &other) return *this;
        delete[] data_;
        data_ = other.data_;
        size_ = other.size_;
        other.data_ = nullptr;
        other.size_ = 0;
        return *this;
    }
```

`std::move(x)` doesn't move anything by itself - it's just a cast that says "treat `x` as an rvalue," giving the compiler permission to pick the move overload instead of the copy overload. After `Buffer c = std::move(a);`, `a` is still a valid object (you can destroy it or reassign it), but its internal pointer is gone. Relying on `a`'s old contents after moving from it is a bug, just not a memory-safety one - `a.data()` now returns `nullptr`, and the class contract only promises "safely destructible," not "unchanged."

Mark move operations `noexcept` whenever you can. Containers like `std::vector` check for this: if your move constructor might throw, `std::vector` falls back to copying when it resizes, silently giving up the speed you wrote the move constructor for.

## The Rule of Five (and the Rule of Zero)

**The Rule of Five:** if your class needs to define *any one* of destructor, copy constructor, copy assignment, move constructor, or move assignment, it almost always needs to define *all five*, deliberately. The reasoning is symmetric: needing a custom destructor is a signal you're managing a resource by hand, and a hand-managed resource needs hand-written copy and move behavior too, or you're back to the shallow-copy landmine. (Older code you'll encounter follows the **Rule of Three** - destructor, copy constructor, copy assignment - from before C++11 added move semantics; the idea is the same, just missing the two move operations.)

**The Rule of Zero**, and this is the one experienced C++ programmers actually reach for: **don't manage raw resources in your own classes at all.** Let your members be `std::string`, `std::vector`, `std::unique_ptr` (phase 13) - types that already correctly implement their own Rule of Five. Then write *none* of the five yourself, and let the compiler generate correct member-wise copy and move for free:

```cpp
class Buffer {
public:
    Buffer(size_t size) : data_(size) {}   // std::vector<int> owns the memory
    size_t size() const { return data_.size(); }
private:
    std::vector<int> data_;
};
```

This `Buffer` has no destructor, no copy operations, no move operations - and is correct, because `std::vector` already solved this problem for you. The lesson of this entire phase, distilled: write the Rule of Five when you're the one holding a raw resource; reach for the Rule of Zero and let RAII types do the holding whenever you can. Most real C++ code should look like the second `Buffer`, not the first - you now understand *why* the first one works, so you'll recognize it (and its bugs) when you meet it in the wild.

## Recap

- C++ objects have value semantics: `b = a` builds or overwrites a real object, and every type needs an answer for how.
- The compiler generates all five special member functions by default, member-wise - correct for RAII members, silently wrong (shallow copy) for raw pointers/handles you own.
- A shallow copy of an owning pointer means two objects think they own one resource, leading to double free / use-after-free when both destructors run.
- Write a copy constructor and copy assignment operator to make copying deep and correct; allocate-before-release keeps assignment safe if allocation throws.
- Move operations (`T&&`, `std::move`) let you steal a temporary's resources instead of copying them, and should be `noexcept`.
- Rule of Five: define one of {destructor, copy ctor, copy assign, move ctor, move assign} and you likely need all five.
- Rule of Zero: prefer RAII members (`std::string`, `std::vector`, `unique_ptr`) so you never have to write any of the five yourself.

### Check yourself

```quiz
[
  {
    "q": "Why does the compiler-generated copy constructor break the raw-pointer Buffer class in this phase?",
    "choices": [
      "It copies the pointer's address, so both objects end up owning and eventually freeing the same heap array",
      "It forgets to copy size_, leaving the new object with size 0",
      "It calls the destructor on the original object as soon as the copy finishes"
    ],
    "answer": 0,
    "explain": "A member-wise copy duplicates the pointer value, not the array it points to - that's the shallow copy that leads to a double free."
  },
  {
    "q": "What does std::move(x) actually do?",
    "choices": [
      "It immediately relocates x's data to new memory",
      "It casts x to an rvalue reference, which just gives the compiler permission to pick a move overload instead of a copy",
      "It deletes x's contents right away so nothing can use them by accident"
    ],
    "answer": 1,
    "explain": "std::move moves nothing by itself - it's a cast that makes the move constructor or move assignment eligible to be chosen."
  },
  {
    "q": "Under the Rule of Zero, why can a class whose only members are std::string and std::vector skip writing all five special member functions?",
    "choices": [
      "Because std::string and std::vector never allocate heap memory, so there's nothing to copy or move",
      "Because those member types already implement correct copy and move themselves, so the compiler-generated member-wise versions are correct",
      "Because the Rule of Zero disables copying entirely, so the question never comes up"
    ],
    "answer": 1,
    "explain": "The compiler-generated special members just call each member's own copy/move - which is already correct when every member is an RAII type."
  }
]
```


---

# Operator Overloading

Here's a question that sounds like a trick but isn't: why is `1 + 2` valid C++, but `p1 + p2` for two `Point` objects a compile error, unless *you* do something about it? `+` isn't magic. It's just a function with unusually short, symbolic syntax. `int::operator+` (conceptually) is built into the language for the built-in types. For your own types, nothing calls it unless you write it.

**Operator overloading is defining a function that runs when someone writes an operator symbol against your type.** That's the whole idea. `a + b` on a class you wrote is sugar for a function call - either `a.operator+(b)` or `operator+(a, b)`, depending on how you defined it. The compiler sees `+` between two `Point`s, looks for an `operator+` that accepts those types, and calls it, exactly like it would resolve any other overloaded function name (phase 4). You're not changing what `+` *means* to the language. You're teaching it what `+` means *for your type*.

This phase assumes you're comfortable with classes (phase 6) and constructors/destructors (phase 7). We won't re-cover `operator=` here - assignment is part of the Rule of Five from phase 8, and it deserves that phase's full attention to copies, moves, and self-assignment. This phase is about the *other* operators: arithmetic, comparison, indexing, and the one you've been using since phase 1 without knowing it was overloaded - `<<` on `std::cout`.

## Member vs. free function: who's on the left?

An operator function can be written two ways, and the choice isn't stylistic - it's forced by *which side of the operator your type is on*.

**As a member function**, the left operand is the implicit `this`:

```cpp
struct Point {
    double x, y;
    Point operator+(const Point& other) const {
        return Point{x + other.x, y + other.y};
    }
};

Point a{1, 2}, b{3, 4};
Point c = a + b;   // calls a.operator+(b)
```

This works because `a`, the left operand, is a `Point` - it has the member to call.

**As a free (non-member) function**, neither operand needs to be `this`:

```cpp
Point operator+(const Point& lhs, const Point& rhs) {
    return Point{lhs.x + rhs.x, lhs.y + rhs.y};
}
```

Both versions produce identical behavior for `a + b`. So why would you ever pick the free function? Because sometimes the left operand *isn't your type*, and a member function can't help you there.

## The stream operator: why `<<` has to be free

You've written `std::cout << x` in every example in this guide. `<<` is an operator on `std::ostream`, overloaded to mean "print" instead of its original meaning "bit-shift left" (that's the C legacy showing - `<<` was borrowed, not invented, for streams). When you want `std::cout << myPoint` to work, think about what a member function would require: `operator<<` would need to be a member of `std::ostream`, since the left operand is the stream. You can't add members to a class you don't own. So the stream operator for a custom type is **always a free function**:

```cpp
#include <ostream>

std::ostream& operator<<(std::ostream& os, const Point& p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

std::cout << a << "\n";   // (1, 2)
```

Two details matter here. First, it takes `std::ostream&` and *returns* `std::ostream&` - that's what makes chaining (`os << a << " and " << b`) work, the same reason `std::cout` itself returns a reference to itself from every `<<`. Second, if `operator<<` needs to read `Point`'s private members, it can't - unless you either expose the data (as we did, with public `x`/`y`) or declare it `friend` inside the class:

```cpp
class Point {
    double x, y;
public:
    Point(double x, double y) : x(x), y(y) {}
    friend std::ostream& operator<<(std::ostream& os, const Point& p);
};

std::ostream& operator<<(std::ostream& os, const Point& p) {
    return os << "(" << p.x << ", " << p.y << ")";   // friend, so private x/y are visible
}
```

`friend` is a narrow, deliberate exception: this one specific free function, and only this one, gets access to `Point`'s private members. It's not a general escape hatch - use it for exactly this pattern (operators and closely-coupled helper functions), not as a way to avoid writing accessors.

## Comparison operators

`operator==` follows the same member-or-free choice as `+`. The natural spelling compares field by field:

```cpp
bool operator==(const Point& lhs, const Point& rhs) {
    return lhs.x == rhs.x && lhs.y == rhs.y;
}
bool operator!=(const Point& lhs, const Point& rhs) {
    return !(lhs == rhs);
}
```

Notice `operator!=` is defined *in terms of* `operator==` rather than repeating the comparison. That's not laziness, it's a correctness habit: if someone later changes what "equal" means for `Point`, there's exactly one place to update, and `!=` stays consistent by construction. (If your compiler targets C++20, the language will synthesize `!=` from `==` automatically - one less function to write - but understanding the manual version is what tells you *why* that shortcut is safe.)

## Indexing with `operator[]`

Container-like types overload `operator[]` to give array syntax. The trick worth knowing here is the **const overload pair**:

```cpp
class IntBox {
    std::vector<int> data;
public:
    int& operator[](std::size_t i) { return data[i]; }
    const int& operator[](std::size_t i) const { return data[i]; }
};
```

The non-const version returns `int&` - a reference you can assign through, so `box[0] = 5;` compiles. The const version returns `const int&` - read-only, called when `box` itself is `const`. The compiler picks whichever overload matches the const-ness of the object you're indexing. Without the const version, a `const IntBox&` parameter couldn't be indexed at all. This pair shows up constantly in real STL-style container code, which is exactly what you're about to look at in phases 11 and 12.

## What not to overload

Operator overloading has one famous cautionary tale: `std::cin >> x` and `std::cout << x` mean "extract" and "insert," borrowed from bit-shift for a purpose that has nothing to do with shifting bits. It works because streams committed to it consistently and everyone learned the convention. That's the bar: **an overloaded operator should do the thing a reader would guess from its symbol**, applied to your type. `+` should combine two things into a bigger thing. `==` should mean "these are equivalent," not "start a network request." If you find yourself overloading `+` to mean "log this and return void," stop and write a named function instead - `point.add(other)` is clearer code than a `+` that lies about what it does.

A few overloads are also just off the table. `&&`, `||`, and `,` can technically be overloaded, but doing so throws away short-circuit evaluation and sequencing guarantees the built-in versions have, which surprises callers in ways that are hard to debug - avoid them. You also can't overload an operator when *both* operands are built-in types (`int + int` can't be redefined), and you can't invent new operator tokens; you're restricted to the fixed set the language already has symbols for.

## Recap

1. Operator overloading is a function call in disguise: `a + b` becomes `a.operator+(b)` or `operator+(a, b)`.
2. Use a **member function** when your type is always the left operand; use a **free function** when it isn't - `operator<<` for printing is the standard example, since the left operand is `std::ostream`, not your class.
3. `friend` grants one specific outside function access to private members - the right tool for stream operators that need to read your class's internals.
4. Define `operator!=` in terms of `operator==` so there's one source of truth for equality.
5. `operator[]` typically comes in a const/non-const pair, returning `T&` or `const T&` to match the caller's access level.
6. Overload only what reads naturally from the symbol. If the operator's meaning isn't obvious at a glance, write a named function instead.

### Check yourself

```quiz
[
  {
    "q": "Why must `operator<<` for printing a custom type be written as a free function, not a member of your class?",
    "choices": [
      "Member operators can't return a value, so chaining wouldn't work",
      "The left operand in `std::cout << myPoint` is `std::ostream`, which you can't add members to",
      "Free functions run faster than member functions in C++",
      "`<<` can only be a member function when overloaded for arithmetic types"
    ],
    "answer": 1,
    "explain": "A member function is invoked through its left operand; since the left operand here is std::ostream, not your class, only a free function can be the one called."
  },
  {
    "q": "Why does the guide define `operator!=` by calling `operator==` and negating it, instead of writing a separate field-by-field comparison?",
    "choices": [
      "Negation is faster at runtime than a second comparison",
      "C++ requires operator!= to be implemented in terms of operator==",
      "If the definition of equality changes later, there's only one place to update, so != stays correct automatically",
      "Only operator== is allowed to access private members"
    ],
    "answer": 2,
    "explain": "Deriving != from == means there's a single source of truth for what 'equal' means, so a later change to == can't leave != out of sync."
  },
  {
    "q": "A function takes `const IntBox&`. What lets it call `box[0]` inside that function?",
    "choices": [
      "A single `operator[]` returning `int&` is enough",
      "A const `operator[]` returning `const int&`, callable on a const object, must exist",
      "Marking `operator[]` as `friend`",
      "Making `operator[]` a template function"
    ],
    "answer": 1,
    "explain": "The compiler picks the overload matching the object's const-ness; without a const version, a const IntBox can't be indexed at all."
  }
]
```


---

# Templates & Generic Programming

Go back to [Phase 8: Copy, Move & the Rule of Five](08-copy-move-and-the-rule-of-five.md) and [Phase 9: Operator Overloading](09-operator-overloading.md) for a second and picture what you've built so far: a `Matrix` class, say, that overloads `+`, has proper copy and move constructors, and cleans up after itself with RAII. It's solid. But it only works for `Matrix<double>`. What if someone needs a matrix of `int`, or `float`, or a custom fraction type? Do you copy-paste the whole class and change one word?

That's the problem templates exist to solve, and it's the last big piece of the puzzle before you're fluent in modern C++. Once you have templates, the STL (next two phases) stops being a black box of container types you memorize and becomes something you understand from the inside, because the STL *is* templates, top to bottom.

## The mental model: templates are a blueprint, not a function

Here's the idea that makes everything else click: **a template is not code. It's a recipe for generating code.** When you write a function template, nothing compiles yet - there's no machine code sitting in your binary for it. The compiler waits until it sees you actually *use* the template with a specific type, and only then does it generate a real, concrete function for that type. This is called **instantiation**.

Compare that to what you might expect from other languages. In Python, a function just works with whatever type you throw at it, checked at runtime. In Java, generics are largely a compile-time-only illusion (type erasure) - the bytecode doesn't know or care what `T` was. C++ templates are neither of those. They generate a **real, fully-typed, separately-optimized function or class for every distinct type you instantiate with.** Call `max(3, 5)` and `max(3.0, 5.0)` in the same program, and the compiler produces two completely separate functions under the hood, each one just as fast as if you'd hand-written it for that exact type. This is why templates are sometimes called "compile-time polymorphism" or "zero-cost generics" - you get the flexibility of generic code with none of the runtime overhead of, say, virtual dispatch (which you'll meet properly in [Phase 14: Inheritance & Polymorphism](14-inheritance-and-polymorphism.md)).

The tradeoff is real too: more instantiations means more generated code (longer compile times, bigger binaries in some cases), and template error messages have a well-earned reputation for being long and intimidating - though modern compilers (and C++20 concepts, which constrain templates to give clearer errors) have gotten much better about this.

## Function templates

Let's build the thing that motivates all of this: a `max` function that works for any comparable type.

```cpp
#include <iostream>

template <typename T>
T my_max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << my_max(3, 7) << "\n";        // T deduced as int
    std::cout << my_max(2.5, 1.1) << "\n";     // T deduced as double
    std::cout << my_max('a', 'z') << "\n";     // T deduced as char
}
```

```console
7
2.5
z
```

Read `template <typename T>` as "for some type `T`, which I'll figure out or you'll tell me." `T` is a placeholder - a **template parameter** - that stands in for a real type. You could write `template <class T>` instead of `typename T`; they mean exactly the same thing here, it's purely historical (`typename` came later, and most style guides now prefer it for clarity). When you call `my_max(3, 7)`, the compiler looks at the argument types, deduces `T = int`, and generates a version of `my_max` where every `T` is replaced with `int`. Call it again with doubles, and it generates a second, entirely separate version with `T = double`. Neither version pays any price for the other existing.

**When deduction fails or you want to be explicit,** you can spell out `T` yourself:

```cpp
my_max<double>(3, 7.5);   // force T = double, converting 3 to 3.0
```

## Class templates

The same idea applies to whole classes - this is how you'd finally write that `Matrix<T>` or a generic `Box<T>` that holds one value of any type.

```cpp
template <typename T>
class Box {
public:
    explicit Box(T value) : value_(value) {}

    T get() const { return value_; }
    void set(T value) { value_ = value; }

private:
    T value_;
};

int main() {
    Box<int> b1(42);
    Box<std::string> b2("hello");

    std::cout << b1.get() << " " << b2.get() << "\n";
}
```

```console
42 hello
```

`Box<int>` and `Box<std::string>` are, to the compiler, two completely unrelated classes - as unrelated as if you'd written `BoxInt` and `BoxString` by hand. The template just saved you from writing them by hand. Notice you write out member functions the same way you always have; the only new syntax is the `template <typename T>` header above the class, and then `T` is just an ordinary type name everywhere inside it.

You've actually already used a class template without necessarily naming it that: `std::vector<int>` *is* `vector` instantiated with `T = int`. `std::vector` itself, uninstantiated, isn't a type at all - it's a template. `std::vector<int>` and `std::vector<double>` are two different, unrelated types that happen to share source code.

## Multiple template parameters

Templates aren't limited to one placeholder type. A `Pair` that holds two possibly-different types needs two:

```cpp
template <typename T1, typename T2>
class Pair {
public:
    Pair(T1 first, T2 second) : first_(first), second_(second) {}

    T1 first() const { return first_; }
    T2 second() const { return second_; }

private:
    T1 first_;
    T2 second_;
};

int main() {
    Pair<std::string, int> age("Alice", 30);
    std::cout << age.first() << " is " << age.second() << "\n";
}
```

This is, essentially, `std::pair` - the standard library ships its own version of exactly this, because the pattern is so common.

## Non-type template parameters

Template parameters don't have to be types - they can be compile-time *values* too, most commonly used for fixed-size arrays:

```cpp
template <typename T, int N>
class FixedArray {
public:
    T& operator[](int i) { return data_[i]; }
    int size() const { return N; }

private:
    T data_[N];
};

int main() {
    FixedArray<double, 5> scores;   // N = 5 baked in at compile time
    scores[0] = 9.5;
    std::cout << scores.size() << "\n";   // 5
}
```

Here `N` is a value, not a type, fixed at compile time for each instantiation - `FixedArray<double, 5>` and `FixedArray<double, 10>` are different types with different-sized internal arrays, and no heap allocation is needed because the compiler knows the exact size at compile time. `std::array<T, N>`, which you'll meet in the next phase, is built exactly this way.

## Template specialization: an escape hatch for special cases

Sometimes the generic version of a template is wrong, or just inefficient, for one particular type. **Specialization** lets you say "for this specific type, use this different implementation instead."

```cpp
template <typename T>
void print_type(T value) {
    std::cout << "generic value: " << value << "\n";
}

// full specialization for bool
template <>
void print_type<bool>(bool value) {
    std::cout << "boolean: " << (value ? "true" : "false") << "\n";
}

int main() {
    print_type(42);     // generic value: 42
    print_type(true);   // boolean: true
}
```

`template <>` with no parameters, followed by `<bool>` after the function name, says "this isn't a new template - it's a hand-written override for exactly `T = bool`." The compiler prefers the specialization whenever the type matches, and falls back to the generic version otherwise. You won't reach for this often as a beginner, but it's worth recognizing when you see it in library code, because it explains why, say, `std::vector<bool>` behaves so strangely compared to every other `std::vector<T>` - it's a specialization with a totally different internal representation (packed bits instead of real `bool`s).

## Templates vs. overloading vs. `void*`

If you came from C, you might reach for `void*` to write "generic" code - a function that takes any pointer, casts it internally, and hopes for the best. Templates replace that whole pattern and do it *safely*: no casting, no losing type information, and the compiler catches type errors at the call site instead of you finding out at 2am that you passed the wrong struct. If you came from a language with function overloading, templates are what you reach for when you'd otherwise have to write the *same body* for every overload - overloading is for genuinely different logic per type; templates are for identical logic, different type.

## What error messages look like (and how to read them)

Template errors can be long because the compiler is often reporting the error at the point of *instantiation*, several layers deep. The trick is to read from the bottom or top consistently (compilers differ) and look for the *first* concrete type mismatch - the rest is usually the compiler explaining how it got there.

```cpp
template <typename T>
T add(T a, T b) { return a + b; }

struct Point { int x, y; };

int main() {
    Point p1{1, 2}, p2{3, 4};
    add(p1, p2);   // Point has no operator+
}
```

```console
error: invalid operands to binary expression ('Point' and 'Point')
    return a + b;
             ^ ~
note: in instantiation of function template specialization 'add<Point>' requested here
    add(p1, p2);
    ^
```

Read it the same way you learned to read borrow-checker errors if you've seen Rust, or ordinary compile errors elsewhere: the real problem (`Point` has no `+`) is stated plainly, and the "in instantiation of" note is just the compiler showing its work - which specific instantiation triggered the failure. Once you know to look for that pattern, template errors stop being scary walls of text.

## Recap

1. A template is not code - it's a **blueprint**. The compiler generates a real, concrete function or class only when you **instantiate** it with an actual type, and each instantiation is fully independent, fully optimized code.
2. `template <typename T>` (or `class T`, identical meaning) declares a placeholder type; the compiler usually **deduces** it from your arguments, or you can specify it explicitly with `func<Type>(...)`.
3. **Class templates** work the same way - `std::vector<int>` is nothing more than `vector` instantiated with `T = int`, and it's an unrelated type to `std::vector<double>`.
4. **Non-type template parameters** (like `int N`) let a compile-time value, not just a type, shape the generated code - the basis for `std::array<T, N>`.
5. **Specialization** (`template <>`) lets you override the generic implementation for one specific type when the general version is wrong or slow for it.
6. This is compile-time, zero-runtime-cost generic code - the mechanism the entire STL is built on, which is exactly where you're headed next.

## Quick check

Test yourself on the idea that makes everything else in this phase click - that a template generates real, separate code per type, at compile time:

```quiz
[
  {
    "q": "When you call `my_max(3, 7)` and `my_max(2.5, 1.1)` in the same program, what does the compiler actually produce?",
    "choices": [
      "One generic function that branches on the argument type at runtime",
      "Two completely separate, fully-typed functions - one for `int`, one for `double` - generated at compile time",
      "A single function using `void*` internally, cast back to the right type on each call",
      "Nothing extra - templates are erased before compilation, like Java generics"
    ],
    "answer": 1,
    "explain": "Each distinct type you instantiate a template with gets its own real, separately-compiled function or class - this is why templates carry zero runtime overhead, unlike a branch-on-type function or Java's type erasure."
  },
  {
    "q": "`std::vector<int>` and `std::vector<double>` - what's the relationship between them, according to the compiler?",
    "choices": [
      "They're the same type, just displayed differently",
      "`std::vector<double>` inherits from `std::vector<int>`",
      "They are two completely unrelated types that happen to share the same source code",
      "They share one underlying implementation chosen at runtime"
    ],
    "answer": 2,
    "explain": "A class template isn't a type by itself - `std::vector` only becomes a real type once instantiated with a `T`, and each instantiation is as unrelated to the others as if you'd hand-written separate classes."
  },
  {
    "q": "You write a full specialization `template <> void print_type<bool>(bool value) { ... }` alongside the generic `print_type<T>`. What happens when you call `print_type(true)`?",
    "choices": [
      "A compile error, since you can't have two versions of the same function",
      "The generic version always wins, since it was defined first",
      "The compiler picks the `bool` specialization instead of instantiating the generic version",
      "Both versions run, generic first, then the specialization"
    ],
    "answer": 2,
    "explain": "Specialization is an escape hatch: the compiler prefers a matching specialization over generating a new instantiation from the generic template, which is exactly how `std::vector<bool>`'s odd packed-bit behavior comes about."
  }
]
```


---

# The STL: Containers

You've now got the two ingredients that make this phase possible. Templates (phase 10) let you write a
type once and have the compiler stamp out a version for any `T`. RAII and the Rule of Five (phases 7-8)
mean a type can own a resource and manage its own lifetime correctly through copies and moves. Put those
together and you get the **Standard Template Library** - a set of generic, RAII-safe data structures that
ship with every C++ compiler. This phase is about the container half of the STL: the boxes you put your
data in. Phase 12 covers the other half - iterators and algorithms, the tools that work *on* those boxes.

## The mental model: a container is a class template that owns its elements

A `std::vector<int>` is not a language feature. It's an ordinary class, defined with a template, whose
constructor allocates memory, whose destructor frees it, and whose copy constructor deep-copies every
element. You already know how to reason about it - you reasoned about exactly this shape of class in
phase 7 and phase 8. The only new thing is that the library ships dozens of these classes, each with a
different internal layout and a different set of tradeoffs, all wrapped around the same ownership
discipline.

That's the payoff for suffering through RAII and the Rule of Five: you never manually `new` or `delete`
a buffer again. `std::vector` does it for you, correctly, every time.

## `std::vector`: your default container

`std::vector<T>` is a dynamically-sized array: elements sit contiguously in memory, exactly like a C
array, but the vector grows itself as you add elements.

```cpp
#include <vector>
#include <iostream>

int main() {
    std::vector<int> scores;        // starts empty
    scores.push_back(90);
    scores.push_back(85);
    scores.push_back(72);

    for (int s : scores) {          // range-based for loop
        std::cout << s << " ";
    }
    std::cout << "\nsize: " << scores.size() << "\n";
}
```
```
90 85 72
size: 3
```

Under the hood, a vector holds a pointer, a size, and a capacity. When `push_back` would exceed capacity,
it allocates a bigger buffer (typically double the size), moves (or copies) the existing elements into
it, and frees the old one. That's why appending is usually O(1) but occasionally, on a resize, costs O(n) - averaged
out ("amortized") over many pushes, it's still O(1) per element. If you know roughly how many elements
you'll need, call `scores.reserve(1000)` up front to skip the resizing dance entirely.

Because a vector's storage is contiguous, indexing with `scores[i]` is a single pointer offset, O(1),
just like a C array. That contiguity is also *why* vector is the default: it's the most cache-friendly
layout there is, and modern CPUs reward that heavily.

**Rule of thumb: reach for `std::vector` unless you have a specific reason not to.** The rest of this
phase is mostly about what those reasons look like.

## `std::array`: when the size is fixed at compile time

`std::array<T, N>` is a fixed-size array with the size baked into the type, stored inline (no heap
allocation at all, just like the raw C arrays from `c-from-zero`) but wrapped with the same interface as
the other containers - `.size()`, iterators, bounds-checked `.at()`.

```cpp
#include <array>

std::array<int, 3> rgb = {255, 0, 128};
// rgb.push_back(1);  // error: array has no push_back, size is fixed
```

Use it when the count is genuinely fixed and known at compile time (a 3D vector's `x, y, z`, a lookup
table). It has zero allocation overhead compared to `std::vector`, which is the whole reason it exists.

## `std::string`: a container you already met

`std::string` (phase 3) *is* a sequence container under the hood - a dynamically-sized, contiguous
buffer of `char`, with the exact same growth story as `std::vector<char>`. Everything you learn here
about vector's cost model - amortized O(1) append, O(n) insert in the middle, contiguous storage -
applies to `std::string` too.

## The other sequence containers, and when to reach for them

| Container | Layout | Good at | Bad at |
|---|---|---|---|
| `std::vector<T>` | contiguous array | indexing, iterating, appending at the end | inserting/removing in the middle |
| `std::deque<T>` | chunked array | push/pop at *both* ends | less cache-friendly than vector |
| `std::list<T>` | doubly linked list | insert/remove anywhere, O(1), given an iterator | indexing (must walk the list), cache-unfriendly |
| `std::forward_list<T>` | singly linked list | minimal memory per node | forward iteration only |

In practice, `std::deque` shows up when you need a queue (push at the back, pop from the front) and
`std::list`/`std::forward_list` are rare - most "I need to insert in the middle a lot" problems are still
faster with a vector plus a smarter algorithm, because linked-list traversal thrashes the cache. Reach
for `list` only after you've measured that vector is actually the bottleneck.

## Associative containers: looking things up by key

`std::vector` finds elements by position. The associative containers find elements by **key**.

```cpp
#include <map>
#include <string>
#include <iostream>

int main() {
    std::map<std::string, int> age;
    age["Ada"] = 36;
    age["Alan"] = 41;

    if (auto it = age.find("Ada"); it != age.end()) {
        std::cout << "Ada is " << it->second << "\n";
    }

    for (const auto& [name, years] : age) {   // structured bindings, C++17
        std::cout << name << ": " << years << "\n";
    }
}
```
```
Ada is 36
Ada: 36
Alan: 41
```

`std::map<K, V>` keeps its keys **sorted** (it's a balanced binary search tree internally), so lookup,
insert, and erase are all O(log n), and iterating it visits keys in order - notice `Ada` printed before
`Alan` above, even though `Alan` was inserted second. `std::set<T>` is the same idea without the value -
just a sorted collection of unique keys.

`std::unordered_map<K, V>` and `std::unordered_set<T>` are the hash-table versions: no ordering
guarantee, but O(1) average-case lookup instead of O(log n). **Default to `unordered_map` when you don't
care about order** - it's almost always faster in practice. Reach for `map`/`set` when you need sorted
iteration, or when your key type doesn't have a good hash function readily available.

Both families reject duplicate keys by design - inserting `age["Ada"] = 40;` again overwrites the
existing entry rather than adding a second one. If you need duplicates, `std::multimap`/`std::multiset`
exist but come up rarely.

## `std::pair` and `std::tuple`: bundling values without a class

Sometimes you want to return two or three related values without writing a whole struct. That's what
`std::map`'s `it->second` came from - a `std::map` entry is actually a `std::pair<const K, V>`. You can
build one yourself:

```cpp
#include <utility>

std::pair<std::string, int> entry = {"Grace", 47};
std::cout << entry.first << " is " << entry.second << "\n";

auto [name, age2] = entry;   // structured bindings unpack it directly
```

`std::tuple<T1, T2, ...>` generalizes `pair` to any number of elements, accessed with `std::get<0>(t)` or
unpacked the same way with structured bindings. Both are fine for quick, throwaway bundling; once a
grouping of values has a real meaning in your program (a `Point`, a `Result`), give it a named struct -
it reads better and the compiler catches more mistakes for you.

## Containers copy by value - on purpose

Because containers follow the Rule of Five, copying one deep-copies everything inside it:

```cpp
std::vector<int> a = {1, 2, 3};
std::vector<int> b = a;      // full copy - a and b own separate buffers
b.push_back(4);               // a is untouched, still {1, 2, 3}
```

This is value semantics doing exactly what it promised back in phase 8: no aliasing surprises, no two
containers secretly sharing one buffer. It also means copying a large container is genuinely expensive -
pass containers by `const&` into functions that only read them, and use `std::move` when you mean to
transfer ownership rather than duplicate it.

## Recap

- Every STL container is a class template built on RAII: it owns its elements, allocates in its
  constructor, frees in its destructor, and deep-copies on copy.
- `std::vector` is the default: contiguous, cache-friendly, O(1) amortized append, O(1) indexing.
- `std::array` is a fixed-size array with vector's interface but no growth and no heap allocation.
- `std::deque`, `std::list`, `std::forward_list` trade vector's cache-friendliness for cheap insertion
  elsewhere, or push/pop at both ends - reach for them only when measurement says vector isn't enough.
- `std::map`/`std::set` are sorted, O(log n); `std::unordered_map`/`std::unordered_set` are hashed,
  O(1) average - default to the unordered versions unless you need sorted order.
- `std::pair`/`std::tuple` bundle a few values quickly; a named struct is clearer once the grouping means
  something.
- Containers copy by value, deeply, following the same rules as any well-behaved class from phase 8.

Now that your data has somewhere to live, the next phase covers how to actually walk through it and
operate on it without writing a raw loop every time.

## Quick check

Test yourself on the ideas that matter most for choosing and using a container correctly:

```quiz
[
  {
    "q": "A vector is at capacity and you call push_back() one more time. What actually happens?",
    "choices": [
      "It throws an exception - call reserve() first or it will always fail",
      "It allocates a bigger buffer, moves (or copies) the existing elements into it, and frees the old buffer",
      "It silently overwrites the last element instead of growing",
      "It grows by exactly one element to avoid wasting memory"
    ],
    "answer": 1,
    "explain": "That reallocate-move-free step is the occasional O(n) cost behind vector's amortized O(1) append - reserve() lets you skip it if you know the size ahead of time."
  },
  {
    "q": "You need to look up values by key and don't care about iteration order. Which container should you default to?",
    "choices": [
      "std::map, because sorted output is always more useful",
      "std::vector, searching linearly for the key",
      "std::unordered_map, for O(1) average-case lookup",
      "std::list, since it inserts anywhere in O(1)"
    ],
    "answer": 2,
    "explain": "unordered_map's hash table gives O(1) average lookup versus map's O(log n) tree - reach for map only when you specifically need sorted iteration."
  },
  {
    "q": "After `std::vector<int> b = a;` followed by `b.push_back(4)`, what happens to `a`?",
    "choices": [
      "a also gains the 4, since b just references a's buffer",
      "a is untouched - the copy gave b its own separate buffer",
      "a becomes empty because ownership moved to b",
      "It's undefined behavior since both vectors share memory"
    ],
    "answer": 1,
    "explain": "STL containers follow the Rule of Five: copying deep-copies every element, so a and b own independent buffers - unlike a Python list or Java ArrayList reference, there's no aliasing here."
  }
]
```


---

# The STL: Iterators & Algorithms

In [Phase 11](11-the-stl-containers.md) you met the containers: `vector`, `list`, `map`, `unordered_map`, `set`, and friends. Each one stores data differently - contiguous array, linked nodes, a tree, a hash table. Now here's a question that should bother you: how does `std::sort` work on a `vector`? What about `std::find`, does it need a different version for `list` than for `map`?

The answer is the single cleverest idea in the STL, and it's why the library is called the *Standard Template Library* and not just "some containers." **Algorithms never touch containers directly. They only ever touch iterators.** A `vector` and a `list` store memory in totally different shapes, but both can hand out an iterator, and every algorithm only knows how to talk to iterators. That one layer of indirection is what lets dozens of algorithms work correctly on every container that ever existed, or ever will.

## The mental model: an iterator is a generalized pointer

Forget the word "iterator" for a second and think about a raw pointer into an array:

```cpp
int arr[] = {10, 20, 30};
int* p = arr;      // points at arr[0]
*p;                 // 10 - dereference to read
++p;                // now points at arr[1]
p == arr + 3;       // true when p has walked off the end
```

A pointer already does three things: it can be dereferenced (`*p`), it can be advanced (`++p`), and it can be compared to a sentinel to know when to stop. An **iterator** is exactly that idea, generalized to work for containers where "advance" doesn't mean "add 4 bytes." For a `std::list`, advancing means "follow the `next` pointer to the next node." For a `std::map`, it means "walk to the next node in tree order." The syntax `*it`, `++it`, `it == end` stays identical - only what happens underneath changes, and each container hides that difference inside its own iterator type.

Every container exposes exactly this pair:

```cpp
std::vector<int> v = {1, 2, 3, 4, 5};

auto it = v.begin();   // iterator to the first element
auto e  = v.end();     // "one past the last element" - not a valid element!

for (; it != e; ++it) {
    std::cout << *it << " ";
}
```

That `end()` is the detail that trips people up first: it does **not** point at the last element, it points *past* it. `[begin, end)` is a half-open range - you keep going while `it != end`, and you never dereference `end` itself. This is exactly why `for (int i = 0; i < n; ++i)` and `[begin, end)` feel so similar: both describe "start here, stop right before there."

The range-based `for` loop you've been using since [Phase 3](03-types-variables-and-control-flow.md) is literally sugar for the loop above - the compiler rewrites `for (auto x : v)` into a `begin()`/`end()`/`++`/`*` loop for you. You already knew iterators; you just weren't calling them that.

## Why not just write the loop yourself?

You *can* write `for` loops forever and never call an algorithm. So why bother?

```cpp
// The "just write a loop" version
int count = 0;
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0) ++count;
}

// The algorithm version
int count = std::count_if(v.begin(), v.end(),
                           [](int x) { return x % 2 == 0; });
```

Both do the same thing, but the second one names the *intent* - "count things matching a condition" - instead of making you reconstruct that intent from a loop's mechanics. That matters more than it sounds like: a loop can have an off-by-one bug, forget to increment, or accidentally mutate something it shouldn't. `std::count_if` cannot have an off-by-one bug, because the STL authors already got it right once and every caller reuses that correctness. This is the same trade you make reaching for a library function over hand-rolling one - except here the "library" covers searching, sorting, copying, and transforming, uniformly, over every container.

## The `<algorithm>` toolbox

Almost everything lives in `<algorithm>` (with a few numeric ones in `<numeric>`). They all take iterator ranges, not containers:

```cpp
#include <algorithm>
#include <numeric>
#include <vector>

std::vector<int> v = {5, 3, 1, 4, 1, 5, 9};

std::sort(v.begin(), v.end());                    // {1,1,3,4,5,5,9}

auto it = std::find(v.begin(), v.end(), 4);        // iterator to the 4
bool found = (it != v.end());

int total = std::accumulate(v.begin(), v.end(), 0); // sum, starting from 0

std::vector<int> doubled(v.size());
std::transform(v.begin(), v.end(), doubled.begin(),
                [](int x) { return x * 2; });       // doubled = {2,2,6,8,10,10,18}

auto count = std::count_if(v.begin(), v.end(),
                            [](int x) { return x > 3; });

v.erase(std::remove(v.begin(), v.end(), 1), v.end()); // the "erase-remove idiom"
```

That last line looks strange the first time you see it, and it teaches something important about how algorithms are constrained. `std::remove` **cannot** actually shrink the container - it only has iterators, not a reference to the `vector` itself, so it has no way to change the container's size. All it can do is shuffle the elements you want to keep toward the front and return an iterator to the new "logical end." You then call the container's own `.erase()` to actually cut the tail off. This split - algorithms rearrange through iterators, containers own the memory - is the same begin/end boundary showing up again, and it's a rite of passage every C++ programmer hits once.

A binary-search family (`std::lower_bound`, `std::binary_search`) assumes the range is already sorted; running it on unsorted data compiles fine and silently gives you a wrong answer, because the algorithm has no way to check your data's shape, only walk it. That's a common, quiet bug - the fix is always "did I sort first?"

## Iterator categories: not all iterators can do the same things

A `vector`'s iterator can jump five elements at once (`it + 5`), because the underlying memory is contiguous. A `list`'s iterator cannot - to reach five elements ahead it must follow five `next` pointers one at a time. The STL names these capability tiers **iterator categories**:

| Category | Can do | Example container |
|---|---|---|
| Input | read once, `++` forward | `istream_iterator` |
| Forward | read multiple times, `++` forward | `forward_list` |
| Bidirectional | `++` and `--` | `list`, `map`, `set` |
| Random access | `+n`, `-n`, `<`, jump anywhere in O(1) | `vector`, `deque`, `array` |

This is why `std::sort` refuses to compile on a `std::list` - sorting efficiently needs to jump around, which requires random access, and `list`'s iterator doesn't offer it. (`list` ships its own `.sort()` member instead, written to work with what a linked list *can* do: pointer relinking.) The compiler error you'll get is ugly template noise, but the root cause is always this: the algorithm asked for more than that iterator can promise.

## The trap: invalidated iterators

An iterator is a handle into a container's current memory. If the container reallocates or restructures - a `vector` growing past its capacity, or erasing an element - any iterator you were holding can become a dangling reference to memory that's no longer valid:

```cpp
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4);      // may reallocate the whole buffer
*it;                  // undefined behavior - it may point at freed memory
```

The rule of thumb: get a fresh iterator (or index) right before you use it, don't hold one across an operation that might resize or erase from the container. Each container's reference documents exactly which operations invalidate iterators - `vector::push_back` might, `list::insert` never does (nodes don't move), and so on.

## What C++20 ranges do about all this

Writing `v.begin(), v.end()` everywhere gets repetitive, and it's easy to accidentally mix a `begin()` from one container with an `end()` from another. C++20 added **ranges**, which let you write `std::ranges::sort(v)` instead of `std::sort(v.begin(), v.end())` - the range itself carries its own bounds. Under the hood it's still iterators doing the work; ranges are a friendlier front door on the same machinery you just learned. [Phase 16](16-modern-c-auto-lambdas-ranges-and-what-changed-si.md) covers this properly.

## Recap

1. **Iterators are generalized pointers**: `*it` reads, `++it` advances, comparing to `end()` tells you when to stop. Every container exposes `begin()`/`end()`, and `[begin, end)` is half-open - `end()` is never a valid element to read.
2. **Algorithms only know iterators, never containers.** That's the whole trick: one `std::sort` works on every container whose iterators support what it needs.
3. Reach for `<algorithm>` (`sort`, `find`, `count_if`, `transform`, `accumulate`, `remove`) instead of hand-writing loops - the intent is clearer and the correctness is already proven.
4. The **erase-remove idiom** exists because algorithms can rearrange through iterators but can't resize a container - only the container's own `.erase()` can do that.
5. **Iterator categories** (input, forward, bidirectional, random access) describe what an iterator can do; algorithms like `sort` require random access, which is why it won't compile on `list`.
6. Don't hold iterators across operations that might resize or erase - get them fresh, right before use.

### Check yourself

```quiz
[
  {
    "q": "Why can algorithms like std::find or std::count_if work identically on a vector, a list, and a map, even though those containers store data in totally different ways?",
    "choices": [
      "Because algorithms only ever interact with iterators, never with the container's internal representation",
      "Because every container converts to a vector internally before an algorithm runs",
      "Because the STL keeps a separate compiled algorithm implementation for each container type",
      "Because list and map secretly use the same memory layout as vector"
    ],
    "answer": 0,
    "explain": "Algorithms are written purely in terms of *it, ++it, and it == end() - the iterator interface - so they never need to know or care how a container actually stores its elements underneath."
  },
  {
    "q": "After calling std::remove(v.begin(), v.end(), 1) on a vector, why is the vector's size unchanged even though every 1 seems to have been removed?",
    "choices": [
      "std::remove has no reference to the container, only iterators, so it can shuffle elements around but can't resize anything - erase() is what actually removes them",
      "std::remove is a no-op that only searches without modifying anything",
      "std::remove only removes the first matching element it finds",
      "Vectors can never shrink once elements have been added to them"
    ],
    "answer": 0,
    "explain": "Because algorithms only get iterators, not a reference to the container object, std::remove can only overwrite the unwanted elements and return a new logical end; only the container's own erase() member can actually shrink it."
  },
  {
    "q": "Why does std::sort(lst.begin(), lst.end()) fail to compile on a std::list, when the exact same call works fine on a std::vector?",
    "choices": [
      "std::sort needs random-access iterators to jump around efficiently, and list's iterators are only bidirectional - they can only step one node at a time",
      "std::list elements can't be compared with <",
      "std::sort only accepts containers directly, not iterator ranges",
      "list typically holds more elements than vector, so it would be too slow"
    ],
    "answer": 0,
    "explain": "list's iterator category is bidirectional (++ and --), not random access (+n); std::sort assumes it can jump to any position in O(1), so use list's own .sort() member instead, which works with pointer relinking."
  }
]
```


---

# Smart Pointers & Modern Memory Management

If you worked through [C From Zero's chapter on `malloc`/`free`](/guides/c-from-zero/10-dynamic-memory-malloc-and-free), you already know the deal with manual heap memory: every `malloc` needs exactly one `free`, and it's on *you* to make sure that happens on every path through the function, including the early returns and the exceptions you haven't met yet. Forget one path and you leak. Free twice and you corrupt the heap. C++ inherited that same danger through `new` and `delete` - and then, over twenty-some years, built a way out of it.

That way out is not a new rule to memorize. It is the RAII pattern from [Phase 7](07-constructors-destructors-and-raii.md), applied to the one resource that causes the most pain: heap-allocated memory. A **smart pointer** is a small class whose whole job is to own a raw pointer and delete it in its destructor. You stop tracking `delete` calls by hand because you stop calling `delete` at all - the object's lifetime does it for you, automatically, exactly once, on every exit path.

## The mental model: ownership, made explicit in the type

Here's the shift that matters more than any function name. A raw pointer, `T*`, tells you *where* something is but says nothing about *who is responsible for freeing it*. Is this pointer owning the memory it points to, or just observing memory someone else owns? You cannot tell from the type - you have to read the surrounding code, the comments, the documentation, and hope they're accurate and up to date.

Smart pointers put that answer directly into the type:

- **`std::unique_ptr<T>`** - "I am the *only* owner of this. When I die, it dies with me."
- **`std::shared_ptr<T>`** - "Several of us share ownership. The last one out turns off the lights."
- **`T*` or `T&`** (a plain raw pointer or reference) - "I am *borrowing* this. I am not responsible for its lifetime, and I promise not to use it after the real owner is gone."

That third line is the one people miss: raw pointers didn't become bad in modern C++, they became *non-owning by convention*. Ownership questions disappear - the compiler and the type answer them for you.

## `unique_ptr`: RAII for a single heap owner

`unique_ptr` is the default. Reach for it first, always, and only step up to something else when you have an actual reason.

```cpp
#include <memory>
#include <iostream>

struct Widget {
    Widget(int id) : id_(id) { std::cout << "Widget " << id_ << " built\n"; }
    ~Widget() { std::cout << "Widget " << id_ << " destroyed\n"; }
    int id_;
};

void use_widget() {
    std::unique_ptr<Widget> w = std::make_unique<Widget>(1);
    std::cout << "using widget " << w->id_ << "\n";
}   // w goes out of scope here -> destructor runs -> delete happens automatically

int main() {
    use_widget();
    std::cout << "back in main, no leak, no manual delete\n";
}
```

```
Widget 1 built
using widget 1
Widget 1 destroyed
back in main, no leak, no manual delete
```

No `new`, no `delete`, no leak even if an exception had been thrown mid-function - unwinding still runs the destructor, same as any other RAII type. Always build one with `std::make_unique<T>(args...)` rather than `unique_ptr<T>(new T(args...))`: it's shorter, it names the type only once, and it keeps `new` out of your code entirely so there's no raw pointer sitting around waiting to be leaked or deleted twice.

`unique_ptr` cannot be copied - copying it would create two owners, which is exactly the double-free bug RAII exists to prevent. It *can* be moved, using the move semantics from [Phase 8](08-copy-move-and-the-rule-of-five.md):

```cpp
std::unique_ptr<Widget> a = std::make_unique<Widget>(2);
std::unique_ptr<Widget> b = std::move(a);   // ownership transfers to b
// a is now empty (nullptr); only b will delete the Widget
```

That's move semantics doing real, visible work: ownership physically changes hands, and the compiler enforces that only one `unique_ptr` ever points at that `Widget` at a time.

## `shared_ptr`: when ownership is genuinely shared

Sometimes one owner doesn't fit the problem - a cache entry that several parts of your program hold onto, a node in a graph reachable from multiple paths. `shared_ptr` keeps a **reference count** alongside the object: every copy of a `shared_ptr` increments it, every destructor decrements it, and the object is deleted the moment the count hits zero.

```cpp
#include <memory>

std::shared_ptr<Widget> make_shared_widget() {
    return std::make_shared<Widget>(3);
}

int main() {
    std::shared_ptr<Widget> p1 = make_shared_widget();   // count = 1
    {
        std::shared_ptr<Widget> p2 = p1;                 // count = 2, same object
        std::cout << "use count: " << p1.use_count() << "\n";   // 2
    }   // p2 destroyed, count = 1
    std::cout << "use count: " << p1.use_count() << "\n";       // 1
}   // p1 destroyed, count = 0 -> Widget deleted
```

Prefer `std::make_shared<T>(args...)` over `shared_ptr<T>(new T(...))` here too - it does one heap allocation for the object *and* its control block together, instead of two, which is both faster and safer.

The cost is real: every copy touches an atomic counter (so `shared_ptr` is thread-safe to copy, but that safety isn't free), and the type is larger than a raw pointer. Reach for `shared_ptr` when ownership is *actually* shared, not as a default "make the compiler stop complaining" move - that instinct is the `shared_ptr` version of the `.clone()`-to-escape-the-borrow-checker trap Rust programmers warn each other about. Most of the time `unique_ptr`, or no smart pointer at all (a plain value, or a reference), is the right call.

## `weak_ptr`: observing without owning, and breaking cycles

`shared_ptr` has one classic failure mode: a **reference cycle**. If A holds a `shared_ptr` to B, and B holds a `shared_ptr` back to A, each keeps the other's count above zero forever - neither is ever destroyed, even after nothing outside the pair can reach them. That's a leak, the same shape of bug garbage-collected languages call out as a known weakness of naive reference counting.

`weak_ptr` is the fix: it points at an object owned by a `shared_ptr` *without* incrementing the count. It can't be dereferenced directly - you must `lock()` it first, which hands you back a real `shared_ptr` if the object is still alive, or an empty one if it's already gone:

```cpp
struct Node {
    std::shared_ptr<Node> child;
    std::weak_ptr<Node> parent;   // weak: doesn't keep the parent alive
};

void visit_parent(const std::weak_ptr<Node>& wp) {
    if (std::shared_ptr<Node> p = wp.lock()) {
        // parent still alive, safe to use p
    } else {
        // parent has already been destroyed
    }
}
```

The rule of thumb: in a parent/child tree, the parent owns children with `shared_ptr`, children point back at the parent with `weak_ptr`. Ownership flows one direction; observation flows the other.

## Rule of Zero: smart pointers finish the job Phase 8 started

Phase 8 taught you the Rule of Five - if you manage a resource yourself, write all five special members or delete them. Smart pointers let most classes skip that homework entirely. Put a `unique_ptr` or `shared_ptr` in your class as a member, write nothing else, and the compiler-generated destructor, move, and (for `shared_ptr`) copy operations are already correct, because they just call into the member's own RAII machinery.

```cpp
class Document {
    std::unique_ptr<Widget> widget_;   // that's it - no destructor, no copy/move code needed
};
```

This is the **Rule of Zero**: the best number of special member functions to write yourself is zero, achieved by letting every resource-owning member be a type - a smart pointer, a `std::vector`, a `std::string` - that already does RAII correctly. Reserve the Rule of Five from Phase 8 for the rare class that *is* a resource wrapper itself; let smart pointers make every class *above* that layer simple again.

## When to still reach for a raw pointer

Raw pointers aren't gone, and that's fine - they just changed job. Use a raw pointer or a reference when you need to **observe** an object without any claim on its lifetime: a function parameter that just looks at a `Widget` someone else owns, an "current selection" field that points into a container it doesn't manage. The rule that keeps this safe is the same one you'd apply anywhere: never let a raw pointer outlive the owner it points into - which is exactly the dangling-pointer discipline from [C's pointer chapters](/guides/c-from-zero/05-pointers-i-the-mental-model), just with the *ownership* half of the problem already solved by the smart pointer standing behind it.

What you should never do is mix the two worlds carelessly: don't call `delete` on a pointer a `unique_ptr` already owns, and don't build a second `shared_ptr` from the raw pointer inside one you already have (`shared_ptr<Widget>(p1.get())` creates an independent control block that will double-free `p1`'s object). If you need another owning handle, copy the smart pointer, never rebuild one from `.get()`.

## Recap

1. A smart pointer is an RAII class whose destructor calls `delete` for you - it moves memory management from "your discipline" to "the type system."
2. `unique_ptr` is the default: single owner, move-only, built with `make_unique`. Cheap - basically a raw pointer with a destructor.
3. `shared_ptr` is for genuinely shared ownership: reference-counted, built with `make_shared`, copy is not free.
4. `weak_ptr` observes a `shared_ptr`-owned object without extending its life, and breaks the ownership cycles that would otherwise leak forever.
5. The **Rule of Zero**: hold resources in smart-pointer (or container) members and you rarely need to write the Rule of Five yourself.
6. Raw pointers and references still exist, but now mean one thing only: "I'm borrowing this, I don't own it" - never call `delete` on one.

## Check yourself

Test yourself on the idea that matters most here - that ownership now lives in the type, not in your memory of the code:

```quiz
[
  {
    "q": "What does a raw pointer, T*, tell you that a unique_ptr<T> doesn't - and vice versa?",
    "choices": [
      "T* says nothing about who owns the memory; unique_ptr<T> says in the type itself that this is the one and only owner",
      "They mean the same thing - unique_ptr is just a raw pointer with a different name",
      "T* is always non-owning by the language rules, so unique_ptr is only needed for arrays",
      "unique_ptr<T> is guaranteed to be faster at every operation than T*"
    ],
    "answer": 0,
    "explain": "A raw pointer only tells you where something is; you'd have to read comments or docs to know who's responsible for freeing it. unique_ptr puts that answer in the type: single, exclusive owner."
  },
  {
    "q": "A Widget only ever needs one owner, but you reach for shared_ptr anyway 'just to be safe.' What does that actually cost you?",
    "choices": [
      "Nothing measurable - shared_ptr behaves identically to unique_ptr in every way",
      "Every copy touches an atomic reference count, and the type carries a control-block pointer unique_ptr doesn't need, for a sharing guarantee you never use",
      "shared_ptr can't be used with make_shared, so you lose exception safety",
      "shared_ptr forces you to write your own destructor, unlike unique_ptr"
    ],
    "answer": 1,
    "explain": "shared_ptr's reference counting is real overhead - an atomic increment/decrement on every copy plus a heavier type - so it should model genuine shared ownership, not stand in as a default 'safer' choice."
  },
  {
    "q": "In a parent/child tree, Node holds children with shared_ptr and points back at its parent with weak_ptr. What breaks if you swap that weak_ptr for a second shared_ptr instead?",
    "choices": [
      "Nothing changes - weak_ptr and shared_ptr behave identically as long as you call lock() first",
      "The program crashes immediately the first time a Node is created",
      "Parent and child now each keep the other's reference count above zero forever, so neither is ever destroyed even after nothing else can reach them",
      "The compiler rejects the code, since shared_ptr can't point to a Node type"
    ],
    "answer": 2,
    "explain": "Two shared_ptrs pointing at each other form a reference cycle: each object's count never reaches zero, so both leak silently. weak_ptr observes without incrementing the count, which is exactly what breaks that cycle."
  }
]
```


---

# Inheritance & Polymorphism

By now you've built classes (phase 6), you know RAII (phase 7), and you know the Rule of Five (phase 8). All of that was about a single object managing its own state and its own lifetime cleanly. Inheritance and polymorphism are about something different: letting *many* types share an interface, so code written once can work correctly on objects it has never seen.

## The mental model: a family of shapes, one function

Picture a drawing app. It has `Circle`, `Square`, and `Triangle` objects, and one function that draws whatever is on the canvas. Without inheritance, that function needs a `switch` on some "kind" field, and every time you add a new shape you go back and edit it. That's fragile and it's exactly the kind of coupling C++'s object model exists to avoid.

Inheritance lets you say: all of these types **are a** `Shape`. Polymorphism lets you say: when you call `shape.draw()`, run *the right version* for whatever object `shape` actually is, decided at runtime, without the caller needing to know which one it is. The caller just needs a `Shape*` or `Shape&` and the interface `Shape` promises.

This is different from templates (phase 10), which pick a version of a function *at compile time* for a type you name explicitly. Polymorphism here is a runtime decision: the same pointer type can point at a `Circle` today and a `Square` tomorrow, and the correct `draw()` runs either way. That flexibility costs a little (an indirect call through a table), but it buys you code that never needs to change when you add a new shape.

## Base and derived classes

```cpp
class Shape {
public:
    virtual void draw() const {
        std::cout << "some shape\n";
    }
    virtual ~Shape() = default;   // more on this below - don't skip it
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "a circle\n";
    }
};

class Square : public Shape {
public:
    void draw() const override {
        std::cout << "a square\n";
    }
};
```

`class Circle : public Shape` means "a `Circle` *is a* `Shape`, and inherits its public interface." `public` inheritance is the one you'll use almost always in modern C++ - it models a true is-a relationship. (`private`/`protected` inheritance exist but are rare; prefer composition, "has-a," when the relationship isn't really is-a.)

The `virtual` keyword on `draw()` is the whole trick. It tells the compiler: don't hard-wire which `draw` runs at compile time - look it up at runtime based on the actual object. `override` on the derived class's version isn't required, but always write it: it tells the compiler "I intend to override a virtual function here," and the compiler will error if you got the signature wrong (a typo'd parameter list otherwise silently creates an unrelated new function instead of overriding).

## What "runtime" buys you

```cpp
void render(const Shape& s) {
    s.draw();   // which draw() runs? decided at runtime, by the real object
}

int main() {
    Circle c;
    Square sq;
    render(c);   // "a circle"
    render(sq);  // "a square"
}
```

`render` takes a plain `const Shape&`. It has never heard of `Circle` or `Square` specifically. Yet it calls the correct `draw()` every time. That's the payoff: you can add a `Triangle` class next year, and `render` needs zero changes. This is why polymorphism matters for real programs - it's how you write code against an interface instead of an ever-growing list of concrete types.

## How it actually works: the vtable

Don't hand-wave this - it's not magic, it's a table of function pointers. When a class has at least one `virtual` function, the compiler adds a hidden pointer to each object, called the **vptr**, pointing at a per-class **vtable**: an array of function pointers, one slot per virtual function. `Circle`'s vtable slot for `draw` points at `Circle::draw`; `Square`'s points at `Square::draw`.

A call to `s.draw()` compiles down to roughly: "follow `s`'s vptr to its vtable, jump to the `draw` slot, call whatever's there." That's the entire mechanism. It costs one extra pointer dereference per call compared to a normal function call, and one extra pointer of size per object holding any virtual function - real but small, and it's exactly why C++ doesn't make every function virtual by default the way some languages do. You pay for polymorphism only on the classes that use it.

## The pointer/reference rule, and slicing

Polymorphism only works through a **pointer or reference** to the base class. This is the single most common beginner mistake:

```cpp
Circle c;
Shape s = c;      // OOPS: this COPIES just the Shape part of c - "slicing"
s.draw();          // prints "some shape", not "a circle"!

Shape& r = c;      // correct: reference, no copy
r.draw();          // prints "a circle"
```

`Shape s = c;` constructs a plain `Shape` object by copying only the `Shape` portion of `c` - the `Circle`-specific data (and its identity) is sliced off and gone. This isn't a bug in the language; it's just what "pass/store by value" always means in C++ (value semantics, from phase 8), applied to a case where it's rarely what you want. The fix is always the same: store and pass polymorphic objects through `Shape&`, `const Shape&`, or a pointer/smart pointer (phase 13 covers `unique_ptr<Shape>`, the usual home for a collection of shapes).

## Virtual destructors are not optional

```cpp
Shape* s = new Circle();
delete s;   // if ~Shape() is NOT virtual: undefined behavior, Circle's part leaks
```

If you delete a derived object through a base pointer and the base destructor isn't `virtual`, only `~Shape()` runs - `~Circle()` never gets called, so any resources `Circle` owns leak, and technically the whole thing is undefined behavior. The rule is simple and absolute: **any class meant to be used polymorphically through a base pointer must have a virtual destructor.** That's why the `Shape` above declares `virtual ~Shape() = default;` - it costs nothing when unused and prevents a real, common bug when it is.

## Abstract classes and pure virtual functions

Often the base class shouldn't be instantiable at all - "shape" isn't a thing you draw, only circles and squares are. Mark a function **pure virtual** with `= 0`:

```cpp
class Shape {
public:
    virtual void draw() const = 0;   // pure virtual - no body required
    virtual ~Shape() = default;
};
```

A class with any pure virtual function is **abstract**: you cannot create a `Shape` directly (`Shape s;` fails to compile), only derived classes that override every pure virtual function. This is C++'s version of an interface, and it's the right tool whenever the base class exists purely to define a contract, not to provide default behavior.

## Multiple inheritance and the diamond problem

C++ permits a class to inherit from more than one base, unlike many languages. It's genuinely useful for combining independent interfaces (`class Duck : public Flyable, public Swimmable`), but it introduces the **diamond problem**: if `Flyable` and `Swimmable` both inherit from a common `Animal`, a `Duck` ends up with two copies of `Animal` by default - ambiguous and usually wrong. The fix is `virtual` inheritance (`class Flyable : public virtual Animal`), which shares a single `Animal` subobject. This is a corner most codebases avoid by leaning on composition instead; know it exists, but reach for it rarely.

## Recap

Inheritance models "is-a"; a derived class inherits and can override its base's interface. `virtual` makes a function call resolve to the actual object's type at runtime, via a vtable lookup, not the pointer's declared type. Always mark overrides `override` and always give a polymorphic base a `virtual` destructor. Use through pointers/references, never plain values, or you'll get slicing. Pure virtual functions (`= 0`) make a class abstract - a contract with no default implementation. This machinery is what lets you write one function against an interface and have it correctly handle types that don't exist yet.

### Check yourself

```quiz
[
  {
    "q": "You have `Shape& r = someCircle;` and call `r.draw()`. What actually decides which `draw()` runs?",
    "choices": [
      "The actual runtime type of the object behind `r`, found via a vtable lookup",
      "The declared type of the reference, `Shape`, always",
      "Whichever `draw()` was compiled last",
      "The compiler picks whichever override is faster"
    ],
    "answer": 0,
    "explain": "Virtual dispatch follows the object's vptr to its own class's vtable at runtime - the reference's declared type never decides which override runs."
  },
  {
    "q": "You write `Shape s = c;` where `c` is a `Circle`, then call `s.draw()` and get \"some shape\" instead of \"a circle\". Why?",
    "choices": [
      "Assigning to a plain `Shape` (not a reference or pointer) copies only the `Shape` portion of `c` - the `Circle` part is sliced off",
      "`Shape::draw()` isn't marked `virtual`",
      "`Circle::draw()` forgot to call the base class version first",
      "You need to call `draw()` before the copy happens, not after"
    ],
    "answer": 0,
    "explain": "Value assignment to a base type copies only the base subobject; polymorphism only works through a pointer or reference to the base, never a plain value."
  },
  {
    "q": "Why must a class meant to be deleted through a base pointer (like `Shape* s = new Circle(); delete s;`) have a virtual destructor?",
    "choices": [
      "Without it, only the base destructor runs, so any resources the derived part owns never get cleaned up",
      "Virtual destructors are required to make a class abstract",
      "Non-virtual destructors run in the wrong order but never leak anything",
      "It only matters for classes with pure virtual functions"
    ],
    "answer": 0,
    "explain": "A non-virtual base destructor means `~Circle()` never runs on that `delete`, so Circle's resources leak - the object's true type is ignored the same way a non-virtual `draw()` would be."
  }
]
```


---

# Error Handling: Exceptions and Alternatives

C has one tool for reporting failure: return a special value (`-1`, `NULL`, a nonzero code) and hope the caller checks it. Nothing stops you from ignoring the check, and nothing carries the failure automatically through ten layers of function calls. C++ adds a second tool - **exceptions** - built specifically to solve that "carry the failure up automatically" problem, and it works hand in hand with the RAII you learned in Phase 7. Modern C++ then adds a third option, value types that *represent* failure (`std::optional`, `std::expected`), for the cases where exceptions are the wrong tool. This phase is about knowing which one to reach for, and why exceptions and RAII are really the same idea seen from two angles.

## The mental model: two totally different failure paths

**What it actually is.** An exception is a value you `throw` instead of `return`. Throwing doesn't return to your caller normally - it unwinds the call stack, skipping the rest of every function in between, until it finds a `catch` block that wants to handle that type of value. Nothing in between gets to ignore it. That's the entire idea: a way to report "this failed" that *cannot* be silently dropped the way a C return code can.

An error-as-value alternative (`std::optional<T>`, `std::expected<T, E>`, or a plain error code) is the opposite philosophy: failure is just data, returned normally, and the caller has to look at it to get the result at all. Nothing unwinds. Nothing is automatic. But nothing is "invisible control flow" either - you can read top to bottom and see exactly where a function might not give you what you asked for.

Neither one is "the C++ way." Both are. Picking between them is a real design decision, and by the end of this phase you'll have a rule of thumb for making it.

## Throwing and catching

```cpp
#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0.0) {
        throw std::invalid_argument("divide by zero");
    }
    return a / b;
}

int main() {
    try {
        std::cout << divide(10, 2) << "\n";
        std::cout << divide(1, 0) << "\n";   // throws
        std::cout << "never reached\n";
    } catch (const std::invalid_argument& e) {
        std::cout << "caught: " << e.what() << "\n";
    }
}
```

```
5
caught: divide by zero
```

Walk through what happened: the second `divide(1, 0)` call threw. The line after it, `"never reached"`, was skipped entirely - control jumped straight to the matching `catch`. That skip is the whole point. In C, the equivalent bug (forgetting to check a return code) just keeps executing with garbage data. In C++, an uncaught error *cannot* be silently stepped over.

Catch by `const&`, always. Catching by value copies the exception (usually harmless but wasteful); catching by non-const reference invites you to mutate an object nobody else will see anyway. `const&` is the idiom, full stop.

`std::invalid_argument` comes from `<stdexcept>`, which gives you a small hierarchy rooted at `std::exception` (`std::logic_error`, `std::runtime_error`, and their children). Prefer throwing one of these, or deriving your own from `std::exception`, over throwing a raw `int` or `const char*` - a caller who writes `catch (const std::exception& e)` should be able to catch *anything* your code throws and still call `.what()` on it.

```cpp
class ParseError : public std::runtime_error {
public:
    explicit ParseError(const std::string& msg) : std::runtime_error(msg) {}
};

// caller code, anywhere up the call stack:
try {
    parseConfig(path);
} catch (const std::exception& e) {   // catches ParseError too - it's a std::exception
    std::cerr << "config failed: " << e.what() << "\n";
}
```

## Why exceptions and RAII are the same idea

Here's the connection Phase 7 was setting up. Stack unwinding doesn't just jump to the `catch` block and leave a mess behind - as the stack unwinds, C++ calls the **destructor** of every local object that was fully constructed on the way, in reverse order, exactly as if the function had returned normally. That guarantee is the entire reason RAII exists.

```cpp
void process(const std::string& path) {
    std::lock_guard<std::mutex> lock(mtx);   // acquires the mutex
    std::ifstream file(path);                // opens the file
    parseOrThrow(file);                      // throws on bad input
    // ... more work
}                                             // normal exit: lock and file destructed here
```

If `parseOrThrow` throws, `process` never reaches its closing `}` the normal way - but the mutex still unlocks and the file still closes, because unwinding runs `lock`'s and `file`'s destructors on the way out, same as it would on a clean return. If you'd managed the mutex with a manual `mtx.lock()` / `mtx.unlock()` pair instead, the throw would skip the `unlock()` and the mutex would stay locked forever. **RAII is what makes exceptions safe to use.** Without it, every function with a resource would need a `catch`, clean up, `rethrow` just to avoid leaking - which is exactly the boilerplate RAII was invented to delete.

This is also why a destructor must never let an exception escape it. If a destructor throws while the stack is *already* unwinding from a different exception, the program calls `std::terminate` and dies on the spot - there's no sensible way to have two exceptions in flight at once. Destructors are implicitly `noexcept`; keep them that way.

## Exception safety: pick a guarantee

When you write a function that might throw partway through, ask what state it leaves things in if it does. Three levels, from weakest to strongest:

| Guarantee | Meaning |
|---|---|
| **Basic** | Nothing leaks, invariants hold, but the object's exact value after the throw is unspecified. |
| **Strong** | If it throws, the object is unchanged - as if the call never happened (commonly done by building the new state off to the side, then swapping it in only once nothing can fail). |
| **No-throw (`noexcept`)** | The function is guaranteed not to throw at all. |

Most STL operations offer at least the basic guarantee; things like `vector::push_back` offer the strong one. Aim for basic everywhere by default (RAII gets you most of this for free) and reach for strong only where a partial failure would actually be confusing to recover from.

## `noexcept`

```cpp
void swap(Widget& a, Widget& b) noexcept {
    // a move-based swap that truly cannot throw
}
```

`noexcept` is a promise to the compiler and to callers: this function will not throw. It matters for two reasons. First, it documents intent - callers can rely on it. Second, it changes generated code: move constructors and move assignment marked `noexcept` let containers like `std::vector` move elements during a resize instead of copying them (a non-`noexcept` move is considered unsafe to use there, since a throw mid-resize would leave the vector in a broken state). If a `noexcept` function throws anyway, the program calls `std::terminate` immediately - so only mark it when you mean it.

## When exceptions are the wrong tool

Exceptions cost you in a few specific ways worth knowing before you reach for them everywhere:

- **The throwing path is slow.** Stack unwinding, RTTI lookups, and constructing the exception object all cost real time - fine for something rare, bad for something that happens on every user keystroke.
- **Some environments ban them.** Embedded firmware, some game engines, and codebases compiled with `-fno-exceptions` don't have them at all. A library that throws is unusable there.
- **They're invisible in a function signature.** `int parse(const char*)` gives you no clue whether it might throw. A caller has to read the implementation, or trust documentation, to know.

For failures that are a *normal, expected* part of calling the function - "the key might not be in the map," "the input might not parse," "the file might not exist" - a value that carries the outcome is usually the better fit:

```cpp
#include <optional>

std::optional<int> parseInt(const std::string& s) {
    try {
        size_t pos;
        int value = std::stoi(s, &pos);   // pos = how many chars were consumed
        if (pos != s.size()) return std::nullopt;   // trailing junk, e.g. "12abc"
        return value;
    } catch (...) {
        return std::nullopt;   // no value - not an error we need details about
    }
}

if (auto n = parseInt(input)) {
    std::cout << "parsed: " << *n << "\n";
} else {
    std::cout << "not a number\n";
}
```

`std::optional<T>` says "there might not be a value" - no error message, just present-or-absent. When you *do* need to say why it failed, C++23's `std::expected<T, E>` carries either a value or an error object, checked explicitly instead of thrown:

```cpp
#include <expected>

std::expected<int, std::string> parseInt(const std::string& s) {
    try {
        size_t pos;
        int value = std::stoi(s, &pos);
        if (pos != s.size()) return std::unexpected("trailing characters after integer: " + s);
        return value;
    } catch (...) {
        return std::unexpected("not a valid integer: " + s);
    }
}

auto result = parseInt(input);
if (result) {
    std::cout << "parsed: " << *result << "\n";
} else {
    std::cout << "error: " << result.error() << "\n";
}
```

This is the same idea C encodes with a return code and an `errno` you have to remember to check (see c-from-zero's error-handling phase if you want the C-side version) - except `std::expected` makes the "might have failed" part of the type itself, so the compiler forces you to unwrap it rather than trusting you to remember.

## A working rule of thumb

Reach for **exceptions** when the failure is rare, when it needs to propagate through many layers that have nothing useful to do about it themselves (only the top-level caller can decide), and when performance on the failure path doesn't matter. Reach for **`optional`/`expected`/error codes** when failure is a routine, expected outcome (parsing, lookups, validation), when you're in performance-sensitive or no-exceptions code, or when you want the possibility of failure spelled out in the function's own signature. Many real codebases use both: exceptions for "something is fundamentally broken," value types for "this specific operation might not succeed" - and RAII underneath both, quietly guaranteeing that whichever one fires, nothing leaks.

### Check yourself

```quiz
[
  {
    "q": "A function throws partway through, a caller three levels up catches it, and the intermediate functions in between never wrote a try/catch of their own. What happens to those intermediate functions' local variables?",
    "choices": ["They keep their last values, since nothing explicitly cleaned them up", "Their destructors still run, in reverse order, as the stack unwinds", "The program skips destructors to unwind faster, then calls std::terminate"],
    "answer": 1,
    "explain": "Stack unwinding runs the destructor of every fully-constructed local object on the way up, which is exactly what makes RAII safe to combine with exceptions."
  },
  {
    "q": "A lock is managed with a manual mtx.lock() / mtx.unlock() pair instead of std::lock_guard, and the code between them throws. What happens to the mutex?",
    "choices": ["It unlocks automatically during unwinding, same as with lock_guard", "It stays locked forever, because the throw skips the unlock() call", "The throw is blocked from happening while the mutex is held"],
    "answer": 1,
    "explain": "Only RAII objects get their destructors run during unwinding - a manual unlock() call sitting after the throwing code is just skipped."
  },
  {
    "q": "You're writing a function to look up a key that's often missing - a completely normal, expected outcome for the caller. What's the better fit?",
    "choices": ["Throw a custom exception so the caller can't forget to handle the missing case", "Return std::optional<T>, since 'no value' isn't really an error", "Mark the function noexcept and return a null pointer on failure"],
    "answer": 1,
    "explain": "Exceptions are for rare, exceptional failures; a routine 'might not be there' outcome reads better and costs less as a value the caller checks."
  }
]
```


---

# Modern C++: auto, Lambdas, Ranges & What Changed Since C++11

Everything up to this phase - classes, RAII, the Rule of Five, templates, the STL, smart pointers, inheritance - is C++ that would compile in 1998. It's real C++, and you needed all of it. But if you read code written today, it looks different: fewer explicit types, functions defined inline as arguments, loops that read like sentences. That's not a different language. It's the same C++, with roughly a decade of accumulated conveniences layered on top, starting with C++11 in 2011 and continuing through C++14, 17, and 20.

## The mental model: C++11 was a new language wearing old syntax

Before C++11, writing a function that took "some callable thing" meant function pointers or hand-rolled functor classes, and writing a type meant spelling it out in full every time, no matter how long the template instantiation made it. C++11 and its successors didn't change what C++ *can* express - they changed how much of it you have to *type*, and they gave you a few tools (closures, deduced types, pipelines over ranges) that used to require real ceremony to fake.

The three features in this phase's title are the ones that change how ordinary code reads the most: `auto` (stop repeating the type), lambdas (functions as values, written where you use them), and ranges (loops as pipelines). Then we'll tour the smaller changes that add up to "modern C++" as a feel, not just a feature list.

## `auto`: let the compiler write the type

`auto` tells the compiler to deduce a variable's type from its initializer, at compile time - there is no runtime cost and no dynamic typing involved, it's exactly as static as spelling the type out yourself.

```cpp
std::vector<std::string> names = {"Ada", "Grace", "Margaret"};

// Before: you spell out the iterator type in full
for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
    std::cout << *it << "\n";
}

// With auto: the compiler already knows the type - why type it twice?
for (auto it = names.begin(); it != names.end(); ++it) {
    std::cout << *it << "\n";
}
```

`auto` earns its keep most when the real type is long, generic, or simply not something you care to name - an iterator type, a lambda's type, the return type of a template function. It is *not* about avoiding thought: `auto x = 5;` is still exactly `int x = 5;`, just with the compiler reading the right side of `=` to fill in the left. Where `auto` genuinely helps is avoiding a mismatch bug: writing `auto` instead of guessing `int` for something that's actually a `size_t` sidesteps a whole class of narrowing and signedness mistakes, because the type it picks is *always* correct by construction.

Use `auto` when the type is obvious from context (`auto v = std::vector<int>{1, 2, 3};`) or too unwieldy to write. Skip it when naming the type is the useful part of reading the line - `auto result = compute();` tells a reader nothing about what `result` *is*, while `double result = compute();` does.

## Lambdas: functions you can write where you use them

A **lambda** is an anonymous function you define inline, right where you need it, that can capture variables from the surrounding scope. It exists because passing behavior around - "sort by this comparison," "call this when done" - used to mean writing a whole separate named function or functor class just to bundle a few lines of logic.

```cpp
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {5, 2, 8, 1, 9};

    // A lambda passed straight into std::sort as the comparison
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;   // descending
    });

    for (int n : nums) std::cout << n << " ";
    std::cout << "\n";   // 9 8 5 2 1
}
```

`[](int a, int b) { return a > b; }` is the whole lambda: `[]` is the **capture list** (empty here - it uses nothing from outside), `(int a, int b)` is the parameter list, and the body is ordinary code. `std::sort` calls it like any other comparison function; you never had to name it, declare it above `main`, or write a functor class with an `operator()`.

**What a lambda actually is.** Don't treat it as magic: the compiler generates a small class with an `operator()` and, for each captured variable, a member to hold it. `[x](){ return x; }` becomes, roughly, a class holding a copy of `x` with a call operator that returns it. This is why lambdas fit so naturally alongside templates and the STL (phases 10-12) - they're just objects, the same "pay for what you use" value semantics as everything else in this language.

**Captures are the part worth being careful with.** `[]` captures nothing, `[x]` captures `x` by value (a copy, frozen at creation), `[&x]` captures `x` by reference (sees later changes, but dangles if `x` dies first), and `[&]` / `[=]` capture *everything* used in the body by reference or by value respectively. The reference-capture footgun is real: a lambda that outlives the local variable it captured by reference is a dangling reference, same as any other reference outliving its target (phase 5).

```cpp
auto make_adder(int n) {
    return [n](int x) { return x + n; };   // n captured by value - safe to return
}

int main() {
    auto add5 = make_adder(5);
    std::cout << add5(10) << "\n";   // 15
}
```

`make_adder` returns a lambda that outlives the function call, so it captures `n` **by value** on purpose - a copy that travels with the lambda. Capturing `n` by reference here would compile and then misbehave: `n` is a parameter of `make_adder`, gone the moment it returns, leaving the lambda holding a dangling reference. If a lambda's lifetime might outlast the scope it was written in, prefer value captures.

## Ranges: loops as pipelines (C++20)

The **range-based `for`** (`for (int n : nums)`, used above and since C++11) was the first step: no more manually managing an iterator pair just to walk a container. C++20's `<ranges>` library takes the same idea further - instead of nesting algorithm calls or writing a loop with an `if` inside it, you describe a *pipeline* of transformations, read left to right.

```cpp
#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto evens_squared = nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });

    for (int n : evens_squared) std::cout << n << " ";
    std::cout << "\n";   // 4 16 36 64 100
}
```

Read `|` as "then": *take `nums`, then keep only the evens, then square each one.* Compare that to the pre-ranges version - a hand-written loop with an `if` and a `push_back`, or a `std::copy_if` into a temporary vector followed by a separate `std::transform`. The pipeline says what you want, not the bookkeeping to get there, and each `std::views::` step is **lazy**: nothing runs until you actually iterate the result, so no intermediate vectors get allocated just to hold a partial answer.

## The rest of "what changed since C++11," briefly

A few more pieces complete the picture, each solving one specific piece of ceremony:

- **Structured bindings** (`auto [key, value] = *map_it;`) unpack a pair, tuple, or struct into named variables in one line, instead of `.first` / `.second`.
- **Uniform initialization** (`Point p{1, 2};`) works consistently across built-in types, aggregates, and classes with constructors, and refuses narrowing conversions that plain `=` would silently allow.
- **`nullptr`** replaced `NULL` and `0` as the null pointer literal, with an actual pointer type - no more overload resolution picking the `int` version of a function by accident.
- **`if` with an initializer** (`if (auto it = m.find(k); it != m.end())`) scopes a lookup variable to just the `if`/`else`, instead of leaking it into the enclosing block.
- **`constexpr`** lets a function run at compile time when its inputs are known then, computing results before the program even starts.

None of these are large ideas on their own. Together with `auto`, lambdas, and ranges, they're why code written today has a different *texture* from the C++ this guide's phase 2 described as "C with extras" - less ceremony per line, more of each line doing exactly what it says.

## Recap

`auto` deduces a variable's type from its initializer at compile time, with zero runtime cost - reach for it when the type is obvious or unwieldy, skip it when naming the type helps the reader. A lambda is an inline, anonymous function that the compiler turns into a small class with a call operator; capture by value when the lambda might outlive the scope it was written in, by reference only when you're certain it won't. C++20 ranges turn nested loops and chained algorithm calls into lazy, readable pipelines with `|`. Structured bindings, uniform initialization, `nullptr`, initializing `if`, and `constexpr` round out the rest - each one removing a specific piece of ceremony C++98 made you write by hand. None of it replaces the fundamentals from earlier phases; it's the same value semantics and the same RAII, just with less typing to get there.

## Quick check

Test yourself on the ideas that change how modern C++ reads: what `auto` actually does, and what a capture really captures.

```quiz
[
  {
    "q": "What does `auto x = compute();` actually do?",
    "choices": [
      "Deduces `x`'s static type from `compute()`'s return type at compile time - no runtime cost, no dynamic typing",
      "Makes `x` a dynamically-typed variable, like in Python or JavaScript",
      "Delays picking `x`'s type until the program runs, then checks it against `compute()`'s result",
      "Makes `x` accept any type at any point in the program, not just at initialization"
    ],
    "answer": 0,
    "explain": "auto is a compile-time convenience for the compiler to read the initializer's type and fill it in - x ends up exactly as statically typed as if you'd spelled the type out yourself."
  },
  {
    "q": "A lambda captures a local variable `n` by reference (`[&n]`) and is then returned from the function and called later. What happens?",
    "choices": [
      "Undefined behavior - `n` was a local that no longer exists, so the lambda holds a dangling reference",
      "It works fine - the compiler automatically extends `n`'s lifetime to match the lambda's",
      "It's a compile error - C++ never allows returning a lambda that captures by reference",
      "`n` keeps its last value frozen at the moment the function returned"
    ],
    "answer": 0,
    "explain": "A reference capture just stores a reference to the original variable, the same as any other reference - it doesn't keep the variable alive. If the lambda outlives the scope n was declared in, that's a dangling reference, so a lambda that needs to outlive its scope should capture by value instead."
  },
  {
    "q": "Why does the ranges pipeline `nums | std::views::filter(...) | std::views::transform(...)` not allocate an intermediate vector for the filtered results before transforming them?",
    "choices": [
      "Each views:: step is lazy - it describes the transformation but doesn't run it until the result is actually iterated",
      "The compiler automatically merges the filter and transform into a single hand-optimized loop",
      "views::filter secretly reuses the original vector's memory instead of creating a new one",
      "It does allocate one, but the allocation happens on the stack instead of the heap"
    ],
    "answer": 0,
    "explain": "std::views:: adaptors build a lazy pipeline description - nothing runs until the final for loop iterates it, so there's no intermediate container holding a partial answer at any point."
  }
]
```


---

# Undefined Behavior, Gotchas & Where to Go Next

Here's a sentence that sounds impossible until you've lived it: a C++ program can compile without a single warning, run correctly a thousand times, and then one day - on a different machine, or with a different compiler flag, or just because the moon was in a different phase - produce garbage, or crash, or format your hard drive. Nothing in the language stopped this from happening. That's not a bug in your compiler. It's a specific, named concept in the C++ standard, and understanding it is the last piece of the mental model this whole guide has been building toward.

## The mental model: UB isn't an error, it's a promise you broke

**What it actually is.** Most languages define what happens for every program you can write. C++ doesn't. The standard defines a set of rules, and for a specific list of things - reading an uninitialized variable, indexing past the end of an array, dereferencing a null or dangling pointer, signed integer overflow, and dozens more - it says: *if your program does this, the standard makes no promise about what happens next.* That's **undefined behavior (UB)**. Not "an error is thrown." Not "the program crashes." Literally anything is a conforming outcome, including "it appears to work."

**Why this exists.** It sounds like a design flaw, but it's a deliberate trade for speed. If the compiler had to check every array access, every pointer dereference, every arithmetic operation for safety, C++ would need runtime checks everywhere - the exact overhead C++ exists to avoid (recall [Phase 2](02-from-c-to-c-what-changed.md)'s "you don't pay for what you don't use"). Instead, the standard says: *you* guarantee these things won't happen, and in exchange, the compiler is free to generate the fastest possible code assuming you kept that promise. This is called the **as-if rule and UB exploitation**: the optimizer is allowed to assume UB never occurs, and it will restructure, reorder, or delete code based on that assumption. That's why UB isn't just "wrong output" - the compiler might notice a code path implies UB and delete it entirely, including code you thought was unrelated.

**Why people get this wrong.** "It compiled and ran fine, so it must be OK" is the single most dangerous sentence in C++. A program with UB *appearing* to work is not evidence of correctness - it's evidence that this particular compiler, on this particular day, with these particular optimization flags, happened to generate code that didn't visibly break. Change any of those, and the same source can behave differently. UB is a property of the code, not of any one run.

## The gotchas you'll actually hit

You've already met some of these in earlier phases - here they are named as a group, because recognizing the pattern matters more than memorizing the list. Most are UB; the last one (slicing) is the odd one out - it's well-defined, just quietly wrong.

```cpp
// 1. Dangling reference/pointer - the resource is gone, the pointer isn't
int* dangling() {
    int local = 42;
    return &local;          // local's storage ends when the function returns
}                            // caller now holds a pointer to freed stack space

// 2. Out-of-bounds access - no bounds check, ever, on raw indexing
std::vector<int> v = {1, 2, 3};
int x = v[10];               // reads whatever memory happens to be there

// 3. Uninitialized read
int y;                       // no default value for a built-in type
std::cout << y;              // reads garbage - could be anything

// 4. Signed integer overflow (unsigned overflow is well-defined; signed is not)
int max = INT_MAX;
int overflowed = max + 1;    // UB, not a wraparound guarantee

// 5. Use-after-free
int* p = new int(5);
delete p;
std::cout << *p;             // p's memory has been returned to the allocator

// 6. Object slicing - assigning a derived object into a base by value
Circle c;
Shape s = c;                 // NOT UB - this is well-defined, just wrong: only the Shape
                             // part is copied, Circle-ness is silently sliced off
```

Notice the theme: `v[10]` compiles. `int y;` compiles. `*p` after `delete` compiles. None of these are syntax errors - they're semantic promises you broke, and the compiler has no obligation to catch them. This is exactly why [Phase 5's](05-references-vs-pointers.md) advice to prefer references over raw pointers, [Phase 8's](08-copy-move-and-the-rule-of-five.md) RAII discipline, and [Phase 13's](13-smart-pointers-and-modern-memory-management.md) smart pointers all exist: modern C++ style isn't a set of arbitrary preferences, it's a systematic campaign to make entire categories of UB unreachable by construction. `std::vector` with `.at()` bounds-checks. `unique_ptr` can't be use-after-freed if you don't hold onto raw pointers to its contents. A reference can't be null. Writing modern C++ well *is* writing less UB-prone code.

## Finding UB before your users do

You can't spot most UB by reading code - it's invisible at the source level. What you can do is turn on tools that catch it at build and run time:

```bash
# Compile-time: turn on the warnings that catch a lot of this class of bug
g++ -Wall -Wextra -Wpedantic -std=c++20 main.cpp

# Runtime: AddressSanitizer catches out-of-bounds, use-after-free, leaks
g++ -fsanitize=address -g main.cpp -o main && ./main

# Runtime: UndefinedBehaviorSanitizer catches overflow, null derefs, and more
g++ -fsanitize=undefined -g main.cpp -o main && ./main
```

`-Wall -Wextra` costs nothing and catches real mistakes at compile time - there's no reason to ship without them. The sanitizers cost some runtime speed, which is exactly why you run them in debug and test builds, not in the release binary you ship. Treat "does it pass with sanitizers on" as part of your definition of "the tests pass," the same way you'd treat a compiler warning: something to fix, not something to suppress.

## Where to go next

You now have the full shape of the language: the object model, RAII and the Rule of Five, templates and the STL, and modern C++ idioms. From here, C++ branches into specialized worlds, and which one you pick depends on what you want to build:

- **Systems and performance work** - profiling with `perf` or VTune, cache-friendly data layout, and eventually the parts of C that C++ builds on top of; [C From Zero](/guides/c-from-zero) is there if you want that bare-metal half of the story.
- **Concurrency** - `std::thread`, `std::mutex`, and `std::atomic` extend the RAII and ownership ideas from this guide into multi-threaded code, where the same "who owns this, and for how long" questions matter even more.
- **Build systems and packages** - real projects use CMake and a package manager (vcpkg or Conan) instead of a single `g++` command; that tooling is worth learning as soon as a project has more than one file.
- **A domain** - game engines, embedded firmware, high-frequency trading, browser engines, and audio software are all still mostly C++, and each has its own idioms layered on top of everything you just learned.

Wherever you go next, the core habit stays the same: think about ownership and lifetime before you think about syntax, let RAII do the cleanup, and treat any UB warning - from a sanitizer, from `-Wall`, or from your own gut - as a bug report on code that merely looked fine.

## Quick check

Test yourself on the idea this whole phase turns on - that UB is a broken promise, not an error type:

```quiz
[
  {
    "q": "What does it mean when the C++ standard says a construct causes undefined behavior?",
    "choices": [
      "The program is guaranteed to crash immediately",
      "The standard makes no guarantee about what happens next - anything, including code that appears to run correctly, is a conforming outcome",
      "The compiler will refuse to compile the code",
      "The operating system decides what the code does"
    ],
    "answer": 1,
    "explain": "UB isn't a special kind of error, it's the absence of any promise at all - a crash, garbage output, and apparently-correct output are all equally valid outcomes."
  },
  {
    "q": "A coworker says a function is fine because they ran it a thousand times without a crash. What's the flaw in that reasoning?",
    "choices": [
      "Nothing - running it many times without a crash proves the code is UB-free",
      "A program with UB can appear to work for many runs and then break under a different compiler, flag, or machine, because 'it worked' was never a guarantee to begin with",
      "UB only ever affects programs that use raw pointers",
      "Sanitizers can't detect UB once a program has already run successfully"
    ],
    "answer": 1,
    "explain": "UB is a property of the code, not of any one run - a passing run is evidence about that run, not proof the promise was kept."
  },
  {
    "q": "What happens when you add 1 to `INT_MAX` in C++?",
    "choices": [
      "It wraps around to `INT_MIN`, the same way unsigned integers wrap",
      "It's undefined behavior - unlike unsigned overflow, signed overflow has no defined wraparound guarantee",
      "The compiler raises a compile-time error",
      "It saturates and stays at `INT_MAX`"
    ],
    "answer": 1,
    "explain": "Unsigned overflow is well-defined wraparound; signed overflow is UB, so a compiler is free to assume it never happens and optimize accordingly."
  }
]
```
