C++ logoC++v23INTERMEDIATE

C++

Modern C++ reference - syntax, the STL, classes, templates, smart pointers, move semantics, concurrency, and C++20/23 features.

18 min read
cppc++stlsystemsooptemplates

Sign in to mark items as known and track your progress.

Sign in

Getting Started

Your first program, compiling, and running C++.

Hello World

The entry point and console output.

cpp
// Quick Reference
#include <iostream>

int main() {
    std::cout << "Hello, World!\n";
}
💡 std::cout << chains output; std::cin >> reads whitespace-separated input.
⚡ Prefer "\n" over std::endl in hot loops - endl flushes the buffer every call.
📌 main returns int; a missing return in main implicitly returns 0 (success).
🟢 Qualify with std:: rather than "using namespace std;" to avoid name clashes.
basicsio

Compile & Run

Build with a modern standard and useful warnings.

cpp
// Quick Reference
g++ -std=c++23 -Wall main.cpp -o main
./main
💡 Always pass -std=c++23 (or your target) - the default standard is often older.
⚡ -Wall -Wextra surface real bugs early; treat warnings as errors with -Werror.
📌 Angle-bracket includes <...> search system paths; quotes "..." search locally first.
🟢 C++ is compiled ahead of time - fix all compile errors before you get a binary.
compilecli

Variables & Types

Fundamental types, inference, constants, and casts.

Types & auto

Built-in types and type inference.

cpp
// Quick Reference
int i = 42;
double d = 3.14;
bool b = true;
char c = 'A';
auto x = 10;      // inferred int
💡 auto deduces the type from the initializer - great for verbose iterator types.
⚡ Use the ' digit separator (1'000'000) for readable numeric literals (C++14+).
📌 int/long sizes are platform-dependent; use <cstdint> (int32_t) when width matters.
🟢 char holds a single character in single quotes; "A" is a string literal, not a char.
typesauto

const, constexpr & Casts

Immutability and explicit conversions.

cpp
// Quick Reference
const int MAX = 100;         // runtime constant
constexpr int SIZE = 10;     // compile-time constant
int n = static_cast<int>(3.9); // 3
💡 constexpr forces compile-time evaluation; const only promises no mutation.
⚡ Prefer static_cast over C-style casts - it is explicit and easier to grep for.
📌 Mark values const by default; only drop it when you truly need to mutate.
🟢 constexpr values can be used where a compile-time constant is required (array sizes).
constconstexprcasts

Operators

Arithmetic, comparison, logical, and bitwise.

Operators

The everyday operator set.

cpp
// Quick Reference
+  -  *  /  %           // arithmetic
== != <  >  <= >=       // comparison
&& ||  !                // logical
&  |  ^  ~  << >>        // bitwise
cond ? a : b            // ternary
💡 Integer division truncates; make one operand floating-point for a real quotient.
⚡ && and || short-circuit - the right operand is skipped when the result is known.
📌 Bitwise ops (& | ^ << >>) work on the binary representation, not logic (&& ||).
🟢 Prefix ++n avoids a copy vs postfix n++ - relevant for heavy iterator types.
operators

Strings

std::string, string_view, and formatting.

std::string

The owning, mutable string type.

cpp
// Quick Reference
#include <string>
std::string s = "hello";
s.size();  s.substr(1, 3);
s += " world";  s.find("world");
💡 std::string owns its data and has value semantics - assigning copies the contents.
⚡ find returns std::string::npos (not -1) when the substring is absent.
📌 std::stoi/stod parse numbers; std::to_string converts numbers to text.
🟢 Index with s[i] for speed, or s.at(i) for bounds-checked access (throws).
strings

string_view & std::format

Cheap views and modern formatting.

cpp
// Quick Reference
#include <string_view>
#include <format>
void log(std::string_view msg);      // no copy
std::string s = std::format("{} = {}", "x", 42);
💡 Take std::string_view for read-only string params - it avoids copies and binds to literals.
⚡ std::format (C++20) is the type-safe successor to printf and stringstream.
📌 Never return a string_view into a temporary - it dangles once the owner dies.
🟢 std::print (C++23) formats directly to stdout; std::format returns a std::string.
string-viewformat

Control Flow

Branching and looping.

Conditionals & switch

if/else and switch, including init-statements.

cpp
// Quick Reference
if (x > 0) { ... } else if (x < 0) { ... } else { ... }

switch (n) {
    case 1: ...; break;
    default: ...;
}
💡 switch needs an explicit break per case, or execution falls through to the next.
⚡ The C++17 if-init form (if (init; cond)) scopes helpers tightly to the branch.
📌 switch works on integers and enums, not on strings or floating-point values.
🟢 Stacked case labels with no break share one body - a clean way to group values.
conditionalsswitch

Loops

for, range-for, while, and do-while.

cpp
// Quick Reference
for (int i = 0; i < 5; i++) { ... }
for (auto x : container) { ... }   // range-for
while (cond) { ... }
do { ... } while (cond);
💡 Use range-for (for (auto x : c)) when you do not need the index.
⚡ Iterate large elements by const auto& to avoid copying each one.
📌 Use auto& (non-const) in range-for when you need to modify elements in place.
🟢 do-while always runs the body once before testing - handy for input retry loops.
loopsrange-for

Functions

Declaring, overloading, and passing arguments.

Functions & Overloading

Signatures, defaults, and overloads.

cpp
// Quick Reference
int add(int a, int b) { return a + b; }
int add(int a, int b, int c);       // overload
void greet(std::string name = "friend"); // default arg
💡 Overloads are chosen by argument types; return type alone cannot distinguish them.
⚡ Default arguments must be the trailing parameters and live on the declaration.
📌 Declare a prototype (or include its header) before calling a function.
🟢 Trailing return (-> type) pairs well with templates where the type depends on params.
functionsoverloading

Passing Arguments

By value, reference, const reference, and pointer.

cpp
// Quick Reference
void byValue(int x);          // copy
void byRef(int& x);           // can modify caller's variable
void byConstRef(const std::string& s); // no copy, read-only
void byPtr(int* p);           // pointer
💡 Pass large objects by const& to avoid an expensive copy while keeping them read-only.
⚡ Use a non-const reference (int&) when the function must modify the caller's value.
📌 Prefer references over pointers unless the argument is genuinely optional (nullable).
🟢 By-value passing is safe and simple for small types (int, double, pointers).
parametersreferences

References & Pointers

Aliases and addresses.

References & Pointers

The two indirection mechanisms and when to use each.

cpp
// Quick Reference
int n = 5;
int& ref = n;      // alias - must bind on init, never rebinds
int* ptr = &n;     // address of n
*ptr = 10;         // dereference to read/write
int* none = nullptr;
💡 A reference must bind on creation and can never be reseated; a pointer can be null and reassigned.
⚡ Use nullptr (typed) instead of NULL or 0 for null pointers.
📌 Dereferencing a null or dangling pointer is undefined behavior - guard with if (ptr).
🟢 Reach for references by default; use raw pointers only for optional/rebindable indirection.
pointersreferences

Arrays & Vectors

Dynamic and fixed-size sequences.

std::vector

The default resizable array.

cpp
// Quick Reference
#include <vector>
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v[0];  v.size();
v.erase(v.begin());
💡 std::vector is the go-to container - contiguous storage, cache-friendly, resizable.
⚡ reserve(n) up front avoids repeated reallocations when you know the size.
📌 operator[] is unchecked; .at() throws std::out_of_range on a bad index.
🟢 emplace_back constructs the element in place; push_back copies/moves an existing one.
vectorcontainers

std::array & C-arrays

Fixed-size sequences.

cpp
// Quick Reference
#include <array>
std::array<int, 3> a = {1, 2, 3};
a.size();  a[0];
int c[3] = {1, 2, 3};   // C-style
💡 Prefer std::array over C arrays - it carries its size and works with range-for and algorithms.
⚡ A C array decays to a pointer when passed to a function, losing its length.
📌 std::array size is a compile-time constant; std::vector when the size varies at runtime.
🟢 std::array lives on the stack (no heap allocation) - fast for small fixed collections.
arrayc-array

STL Containers

Maps, sets, and tuples.

map & unordered_map

Key-value associative containers.

cpp
// Quick Reference
#include <map>            // ordered
#include <unordered_map>  // hashed
std::unordered_map<std::string, int> m;
m["a"] = 1;  m.at("a");
m.count("a");  m.contains("a"); // C++20
💡 operator[] inserts a default value if the key is missing - use .at() or .find() to just read.
⚡ Iterate with structured bindings: for (auto& [k, v] : map).
📌 std::map is ordered (tree, O(log n)); std::unordered_map is hashed (avg O(1)).
🟢 .contains() (C++20) is the readable way to test membership without inserting.
mapunordered-map

set, pair & tuple

Unique collections and fixed groupings.

cpp
// Quick Reference
#include <set>
std::set<int> s = {3, 1, 2};   // sorted, unique
std::pair<int, std::string> p{1, "a"};
auto t = std::make_tuple(1, 2.0, "x");
💡 std::set stores sorted, unique keys; inserting a duplicate is a no-op.
⚡ Destructure pairs/tuples with structured bindings: auto [a, b] = p.
📌 Access tuple elements by compile-time index: std::get<0>(t).
🟢 Prefer a small struct with named fields over a tuple when the meaning matters.
setpairtuple

Algorithms & Iterators

The <algorithm> and <numeric> workhorses.

Algorithms

Sort, search, transform, and accumulate.

cpp
// Quick Reference
#include <algorithm>
std::sort(v.begin(), v.end());
std::find(v.begin(), v.end(), x);
auto n = std::count_if(v.begin(), v.end(), pred);
💡 Most algorithms take a [begin, end) iterator pair - end is one past the last element.
⚡ std::find/find_if return the end iterator when nothing matches - always compare to .end().
📌 std::accumulate lives in <numeric>, not <algorithm>; its 3rd arg is the initial value.
🟢 C++20 ranges let you write std::ranges::sort(v) without begin()/end() (see Modern C++).
algorithmsiterators

Lambdas & std::function

Anonymous functions and callable wrappers.

Lambdas

Inline callables with captures.

cpp
// Quick Reference
auto add = [](int a, int b) { return a + b; };
int factor = 3;
auto mul = [factor](int x) { return x * factor; };  // capture by value
auto ref = [&sum](int x) { sum += x; };             // capture by reference
💡 [=] captures everything by copy, [&] by reference; name specific vars to be explicit.
⚡ Capturing by reference into a lambda that outlives the variable dangles - prefer by value there.
📌 Add mutable to let a lambda modify its by-value captures between calls.
🟢 std::function stores any callable but adds overhead - use auto for local lambdas.
lambdasfunctional

Enums & Structs

Named constants and plain data.

Enums & Structs

enum class and aggregate structs.

cpp
// Quick Reference
enum class Color { Red, Green, Blue };
Color c = Color::Red;

struct Point { int x; int y; };
Point p{3, 4};
💡 Prefer enum class over plain enum - it scopes names and blocks implicit int conversion.
⚡ struct and class are identical except structs default to public members.
📌 Aggregate init Point{3, 4} sets members in declaration order.
🟢 Give struct members default initializers (int x = 0;) so partial init stays safe.
enumsstructs

Classes & Objects

Encapsulation, lifetime, and operators.

Classes & Constructors

Members, access, and the member initializer list.

cpp
// Quick Reference
class Person {
public:
    Person(std::string n) : name(n) {}
    std::string getName() const { return name; }
private:
    std::string name;
};
💡 Initialize members in the constructor initializer list (: a(x)), not in the body - it avoids double-init.
⚡ Mark member functions const when they do not modify the object - const objects can only call those.
📌 class members are private by default; put the public interface first for readability.
🟢 Give members default initializers so every constructor starts from a known state.
classesconstructors

Destructors, static & RAII

Deterministic cleanup and class-level members.

cpp
// Quick Reference
class File {
public:
    File(const char* p) { f = fopen(p, "r"); } // acquire
    ~File() { if (f) fclose(f); }               // release
private:
    FILE* f = nullptr;
};
💡 RAII: acquire a resource in the constructor, release it in the destructor - cleanup is automatic.
⚡ Destructors run deterministically when an object leaves scope (LIFO order).
📌 static members belong to the class, not any instance, and need one out-of-class definition.
🟢 Smart pointers and containers are RAII types - lean on them instead of new/delete.
destructorsstaticraii

Operator Overloading & Rule of Five

Custom operators and copy/move control.

cpp
// Quick Reference
struct Vec2 {
    double x, y;
    Vec2 operator+(const Vec2& o) const { return {x+o.x, y+o.y}; }
};
std::ostream& operator<<(std::ostream& os, const Vec2& v);
💡 Overload operators to make your types behave like built-ins (a + b, std::cout << v).
⚡ = default (C++20) auto-generates ==, and <=> gives you all comparisons at once.
📌 Rule of Five: defining a destructor/copy/move means you likely need all five (or = default).
🟢 Best case is the Rule of Zero - use RAII members so the compiler writes all five for you.
operator-overloadingrule-of-five

Inheritance & Polymorphism

Base classes and virtual dispatch.

Inheritance

Deriving classes and reusing behavior.

cpp
// Quick Reference
class Animal {
public:
    Animal(std::string n) : name(n) {}
protected:
    std::string name;
};
class Dog : public Animal {
public:
    Dog(std::string n) : Animal(n) {}
};
💡 A derived class must initialize its base via the base constructor in the init list.
⚡ protected members are visible to derived classes but not to outside code.
📌 public inheritance models "is-a"; prefer composition ("has-a") when that fits better.
🟢 C++ supports multiple inheritance, but keep hierarchies shallow to avoid ambiguity.
inheritance

Virtual & Polymorphism

Dynamic dispatch, abstract classes, and casts.

cpp
// Quick Reference
class Shape {
public:
    virtual double area() const = 0;   // pure virtual -> abstract
    virtual ~Shape() = default;        // virtual destructor
};
class Circle : public Shape {
    double area() const override { return 3.14159 * r * r; }
    double r;
};
💡 virtual enables runtime dispatch - the actual object type decides which method runs.
⚡ Always give a polymorphic base class a virtual destructor, or deleting via a base pointer leaks.
📌 A pure virtual (= 0) makes the class abstract - it cannot be instantiated directly.
🟢 override lets the compiler verify you are actually overriding a base virtual.
virtualpolymorphism

Templates & Concepts

Generic code and constraints.

Templates

Function and class templates.

cpp
// Quick Reference
template <typename T>
T max(T a, T b) { return a > b ? a : b; }

template <typename T>
class Box { T value; };
💡 Templates are instantiated per type at compile time - zero runtime overhead.
⚡ Template code usually lives entirely in headers (the definition must be visible to callers).
📌 Non-type parameters (int N) let you parameterize on compile-time values like sizes.
🟢 The compiler deduces T from the arguments - you rarely write maxOf<int>(...) explicitly.
templatesgenerics

Concepts (C++20)

Constrain template parameters readably.

cpp
// Quick Reference
#include <concepts>
template <std::integral T>
T doubleIt(T x) { return x * 2; }
💡 Concepts (C++20) constrain templates so misuse fails with a clear message, not a wall of errors.
⚡ Standard concepts live in <concepts>: std::integral, std::floating_point, std::same_as, ...
📌 requires clauses and constrained auto (std::integral auto) both express the same constraint.
🟢 Concepts replace most SFINAE/enable_if tricks with far more readable code.
conceptstemplates

Smart Pointers & Move Semantics

Automatic memory management and efficient transfers.

Smart Pointers

Owning pointers that free themselves.

cpp
// Quick Reference
#include <memory>
auto u = std::make_unique<int>(42);   // sole owner
auto s = std::make_shared<int>(42);   // ref-counted
std::weak_ptr<int> w = s;             // non-owning observer
💡 Default to unique_ptr (zero overhead); use shared_ptr only when ownership is truly shared.
⚡ Create with make_unique/make_shared - never new - for exception safety and clarity.
📌 unique_ptr is move-only: transfer ownership with std::move, you cannot copy it.
🟢 weak_ptr breaks shared_ptr reference cycles; lock() yields a shared_ptr if still alive.
smart-pointersmemory

Move Semantics

Transfer resources instead of copying.

cpp
// Quick Reference
std::vector<int> a = {1, 2, 3};
std::vector<int> b = std::move(a);   // steal a's buffer, no copy
// a is now valid-but-unspecified (usually empty)
💡 Moving transfers ownership of internal resources (pointers/buffers) instead of deep-copying.
⚡ After std::move(x), x is valid but unspecified - only reassign or destroy it.
📌 std::move is just a cast to rvalue; the actual work happens in the move constructor.
🟢 Mark move operations noexcept so containers like std::vector can move (not copy) on resize.
move-semanticsrvalue

Exceptions

Errors that unwind the stack.

Exceptions

throw, try/catch, and noexcept.

cpp
// Quick Reference
try {
    throw std::runtime_error("boom");
} catch (const std::exception& e) {
    std::cerr << e.what();
}
💡 Catch by const reference (const std::exception&) to avoid slicing and copies.
⚡ Order catch blocks most-derived first - a base std::exception catch would shadow the rest.
📌 Derive custom exceptions from std::exception so generic handlers can catch them.
🟢 RAII makes exceptions safe - resources release during stack unwinding automatically.
exceptionserror-handling

File & Stream I/O

Reading and writing with streams.

File I/O

fstream and stringstream.

cpp
// Quick Reference
#include <fstream>
std::ofstream out("data.txt");
out << "hello\n";
std::ifstream in("data.txt");
std::string line;
std::getline(in, line);
💡 Streams are RAII - the file closes automatically when the fstream leaves scope.
⚡ Always check the stream (if (in)) - opening or reading can fail silently otherwise.
📌 std::getline reads a full line (past spaces); operator>> stops at whitespace.
🟢 std::stringstream is the idiomatic way to build or parse strings piece by piece.
file-iostreams

Concurrency

Threads, synchronization, and async.

Threads & Mutex

Running work in parallel safely.

cpp
// Quick Reference
#include <thread>
#include <mutex>
std::thread t([]{ work(); });
t.join();
std::mutex m;
std::lock_guard<std::mutex> lock(m);
💡 Every std::thread must be joined or detached before it is destroyed, or the program aborts.
⚡ std::jthread (C++20) auto-joins on destruction - prefer it over raw std::thread.
📌 Guard shared data with std::lock_guard/scoped_lock - RAII unlocks even on exceptions.
🟢 Unsynchronized access to shared mutable data from multiple threads is a data race (UB).
threadsmutex

async, future & atomic

Results from background work and lock-free counters.

cpp
// Quick Reference
#include <future>
auto fut = std::async([]{ return 40 + 2; });
int result = fut.get();          // 42

#include <atomic>
std::atomic<int> count{0};
count++;                         // atomic, no mutex
💡 std::async returns a future; call .get() once to retrieve the value (it blocks until ready).
⚡ std::atomic gives race-free single-variable updates without a mutex.
📌 Pass std::launch::async to force a new thread; the default may run lazily on get().
🟢 future.get() rethrows any exception the async task threw - handle it at the call site.
asyncatomic

Numbers, Date/Time & Regex

Math, time, randomness, and pattern matching.

Numbers & Math

<cmath>, <random>, and numeric limits.

cpp
// Quick Reference
#include <cmath>
std::sqrt(16.0);  std::pow(2, 10);
std::abs(-5);  std::round(3.6);
#include <limits>
std::numeric_limits<int>::max();
💡 Use <random> (mt19937 + a distribution), not rand() - it is higher quality and unbiased.
⚡ std::numeric_limits<T> reports the range/precision of any numeric type.
📌 <numeric> adds gcd, lcm, accumulate, iota, and reduce beyond the plain math functions.
🟢 Integer and floating overflow differ: signed integer overflow is undefined behavior.
mathrandom

Date/Time & Regex

std::chrono and std::regex.

cpp
// Quick Reference
#include <chrono>
auto start = std::chrono::steady_clock::now();
// ...
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::steady_clock::now() - start);
💡 Use steady_clock for measuring durations; system_clock for wall-clock/calendar time.
⚡ Raw string literals R"(...)" let you write regex patterns without doubling backslashes.
📌 duration_cast converts between time units; .count() extracts the numeric value.
🟢 std::regex_search finds a match anywhere; regex_match requires the whole string to match.
chronoregex

Modern C++ (C++20/23)

Recent language and library additions.

Utility Types & Structured Bindings

optional, variant, and destructuring.

cpp
// Quick Reference
#include <optional>
std::optional<int> find();
if (auto r = find()) use(*r);

auto [a, b] = std::pair{1, 2};   // structured bindings
💡 std::optional expresses "maybe a value" without null pointers or magic sentinels.
⚡ .value_or(fallback) reads an optional safely; *opt / .value() throw or UB if empty.
📌 std::variant is a type-safe tagged union; access it with std::visit or std::get.
🟢 Structured bindings (auto [a, b] = ...) destructure pairs, tuples, and simple structs.
optionalvariantstructured-bindings

Ranges, format & C++23

Pipelines, formatting, and the newest additions.

cpp
// Quick Reference
#include <ranges>
auto evens = v | std::views::filter([](int x){ return x%2==0; })
               | std::views::transform([](int x){ return x*x; });
💡 Ranges views are lazy and composable with | - nothing runs until you iterate.
⚡ std::expected<T, E> (C++23) returns a value or an error without throwing exceptions.
📌 The spaceship operator <=> = default generates all six comparison operators at once.
🟢 std::print (C++23) needs a recent compiler/libstdc++; std::format works more broadly.
rangescpp23format