Skip to content

Control flow

8.1 — Control Flow Introduction

By default, programs execute sequentially (top to bottom). Control flow statements break this linearity:

  • Conditional branches: Execute code based on a condition (if, switch).

  • Loops: Repeat code multiple times (while, do-while, for).

  • Humps/Halts: Jump to another part of the program or stop it entirely (goto, break, continue, exit).


8.2 & 8.3 — If Statements: Basics and Pitfalls

  • Implicit Blocks: If you don't use curly braces { }, only the single next statement is considered part of the if.

    • Best Practice: Always use braces, even for one-liners, to avoid the "dangling else" problem or accidental logic errors.
  • Chaining: Use else if to create a mutually exclusive chain of conditions.

  • Common Problems: * Using = (assignment) instead of == (equality) inside the condition.

    • Null statements: if (condition); (the semicolon ends the if-statement immediately, doing nothing).

8.4 — Constexpr If Statements

Introduced in C++17, if constexpr allows for compile-time branching.

  • If the condition is false at compile-time, the compiler completely discards the code inside the block.

  • This is primarily used in template programming to handle different data types differently without causing compilation errors for types that don't support certain operations.


8.5 & 8.6 — Switch Statements

A switch is often more readable than a long chain of if-else statements when comparing one variable against multiple constant values.

  • Labels: Must be constant expressions (literals or constexpr).

  • The default label: Executes if no match is found.

  • Fallthrough: Execution continues into the next case unless a break is hit.

    • Use the [[fallthrough]] attribute in C++17 to tell the compiler (and other programmers) that fallthrough is intentional.
  • Scoping: You cannot define variables with initialization inside a case unless you wrap the case in a block { }.


8.7 — Goto Statements

goto allows an unconditional jump to a labeled statement.

  • Why it's "evil": It can create "spaghetti code" that is nearly impossible to follow.

  • The only exception: Occasionally used to break out of deeply nested loops, though modern C++ usually offers better alternatives.


8.8, 8.9 & 8.10 — Loops: While, Do-While, and For

  • While Loop: Checks the condition before executing the body. It may run zero times.

  • Do-While Loop: Executes the body first and then checks the condition. It is guaranteed to run at least once.

  • For Loop: Best for when you know how many times you need to iterate.

    • Structure: for (init-statement; condition; iteration-expression)

    • Off-by-one errors: The most common loop bug (e.g., using <= when you should use <).


8.11 — Break and Continue

  • Break: Immediately exits the current loop or switch statement.

  • Continue: Skips the rest of the current loop iteration and jumps straight to the next condition check (or the iteration expression in a for loop).


8.12 — Halts

Halts stop the program entirely.

  • std::exit(): Cleans up static objects and returns a status code to the OS.

  • std::abort(): Terminates the program immediately without cleanup (usually for critical failures).


8.13, 8.14 & 8.15 — Random Number Generation (RNG)

Computers are deterministic, so they generate "pseudo-random" numbers based on an initial "seed."

  • The Old Way: std::rand() is considered poor quality and should be avoided in modern C++.

  • Mersenne Twister (std::mt19937): The standard for high-quality pseudo-random numbers in C++ (found in the <random> header).

  • The Process:

    1. Seed: Use std::random_device to get a non-deterministic seed.

    2. Engine: Initialize the Mersenne Twister with the seed.

    3. Distribution: Use a distribution (like std::uniform_int_distribution) to map the raw bits into a specific range (e.g., 1 to 6).

  • Global RNG: It is best practice to initialize your RNG engine once (globally or as a static variable) and reuse it, rather than re-seeding every time you need a number.