Open-source libraries used

1 libraries are bundled into this tool's code.

C++ Cheatsheet — Quick Reference

A concise C++17/20 cheatsheet covering syntax, the standard library, classes, templates, and the most common idioms — roughly 80% of everyday scenarios.

C++

C++ C++17 / C++20

ISO C++ (GCC / Clang / MSVC) · Multi-paradigm (OOP · generic · functional) · Static · strong · nominal

Recommended Learning Path

Start with "Hello World and Build Setup" to get a clean compile/run cycle under your belt. Then move through variables, types, control flow, and functions; pick up the standard containers (vector, map) and strings; learn RAII and smart pointers (the memory chapter); then OOP and templates; finally dip into threads, networking, regex, and the build tools as needed. The FAQ chapter is the place to revisit when something surprises you.

1.Hello World and Build Setup

Compile, run, and organize a C++ program from scratch: toolchain, command-line arguments, multiple files, and C++20 modules.

Minimal program

Every C++ program starts executing at main(); returning 0 indicates success. std::cout is the standard output stream, fed via operator<<.

1
2
3
4
5
6
#include <iostream>
int main() {
std::cout << "Hello, world!\n";
return 0;
}

Compile and run

Compile with g++ or clang++: -std selects the C++ standard, -Wall -Wextra enables warnings, -o names the output binary; run with ./app.

1
2
3
4
5
6
7
8
// $ g++ -std=c++17 -Wall -Wextra main.cpp -o app
// $ ./app
// clang++ uses the same flags; MSVC uses cl /EHsc main.cpp
#include <iostream>
int main() {
std::cout << "Built with C++17\n";
return 0;
}

CMake build

CMake is the mainstream cross-platform build system: CMakeLists.txt declares the project and targets, -B sets the build directory, --build triggers compilation; outputs land under build/.

1
2
3
4
5
6
7
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(app LANGUAGES CXX)
add_executable(app main.cpp)
target_compile_features(app PRIVATE cxx_std_17)
// $ cmake -B build && cmake --build build

Command-line arguments

main's argc is the argument count, argv is the argument array, and argv[0] is always the program name. User arguments start at argv[1].

1
2
3
4
5
6
7
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "argc=" << argc << '\n';
for (int i = 0; i < argc; ++i)
std::cout << "argv[" << i << "]=" << argv[i] << '\n';
return 0;
}

Exit code

main's return value becomes the process exit code: 0 means success, non-zero indicates an error category. EXIT_SUCCESS / EXIT_FAILURE read better.

1
2
3
4
5
#include <cstdlib>
int main() {
if (!work()) return 1; // non-zero = failure
return EXIT_SUCCESS; // equivalent to return 0
}

Multi-file build

Declarations live in headers (#pragma once guards re-inclusion), definitions in .cpp files, used via #include. Compile all .cpp files together to link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// math.h —— declaration
#pragma once
int add(int a, int b);
// math.cpp —— definition
#include "math.h"
int add(int a, int b) { return a + b; }
// main.cpp —— usage
#include <iostream>
#include "math.h"
int main() {
std::cout << add(2, 3) << '\n';
}
// $ g++ -std=c++17 math.cpp main.cpp -o app

C++20 modules

Modules replace headers: export module declares a module, export exposes symbols, import pulls them in — faster builds, no macro pollution, but still maturing.

1
2
3
4
5
6
7
8
9
10
11
// math.cppm —— module
module;
#include <cmath>
export module math;
export int sq(int x) { return x * x; }
// main.cpp —— import
import math;
#include <iostream>
int main() { std::cout << sq(5) << '\n'; }
// $ g++ -std=c++20 -fmodules-ts math.cppm main.cpp -o app

Namespace

Namespaces organize symbols to avoid clashes. Qualify with geo::area, or prefer precise using-declarations like using std::cout over using namespace ...

1
2
3
4
5
6
7
8
9
10
#include <iostream>
namespace geo {
constexpr double pi = 3.14159;
double area(double r) { return pi * r * r; }
}
int main() {
std::cout << geo::area(1.0) << '\n';
using std::cout; // precise using-declaration
cout << geo::pi << '\n';
}

2.Variables and Constants

Declarations and type deduction, const/constexpr, references, structured bindings, scope, and C++-style casts.

auto type deduction

auto deduces the type from the initializer and must be initialized immediately. Deduction strips references and top-level const; write const auto& explicitly when needed.

1
2
3
4
auto x = 42; // int
auto d = 3.14; // double
auto s = std::string("hi");
const auto& r = x; // preserve const + reference

const and constexpr

const marks a value as immutable; constexpr guarantees compile-time evaluation, usable for array sizes and template arguments. C++20 constexpr functions can hold more logic.

1
2
3
4
const int n = 42; // runtime or compile-time constant
constexpr int k = 42; // must be a compile-time constant
constexpr int sq(int x) { return x * x; }
static_assert(sq(6) == 36); // compile-time check

References

A reference is an alias for an existing object: it must be initialized and always refers to the same object. Passing by reference avoids copies and lets the callee modify the original.

1
2
3
4
5
int x = 1;
int& r = x; // a reference is an alias; cannot be null or reseated
r = 2; // modifies x
const int& c = x; // read-only view
void bump(int& v) { ++v; } // pass by reference to modify original

Structured bindings

C++17 destructures pair/tuple/struct/array into named variables. Unpacking key/value while iterating a map is the most common use case.

1
2
3
4
5
6
std::pair<int, double> p{1, 2.5};
auto [a, b] = p; // unpack into named variables
std::map<std::string, int> m;
for (const auto& [key, val] : m) {
// use key / val directly
}

Inline variables

Before C++17, non-constexpr globals in headers caused multiple-definition errors; inline variables let multiple translation units share the same object.

1
2
3
4
5
// header.h
struct Config {
static inline int threads = 4; // defined in the header
};
inline constexpr int kMax = 100; // commonly used as a header constant

mutable members

mutable members can be modified inside const member functions — typically used for caches, call counters, or mutexes whose change doesn't alter the object's logical state.

1
2
3
4
struct Counter {
int get() const { ++n_; return n_; }
mutable int n_ = 0; // const function can modify it
};

decltype

decltype yields the declared type of an expression. decltype((x)) (with extra parens) deduces a reference — a common trap; trailing return types are handy in templates.

1
2
3
4
5
int x = 1;
decltype(x) y = 2; // int
decltype((x)) r = x; // int&, extra parens = reference
// trailing return type: return type depends on parameters
template <typename T> auto twice(T v) -> decltype(v + v) { return v + v; }

Scope and shadowing

C++ allows inner scopes to shadow outer names. Avoid shadowing globals — use ::n to reach the global explicitly, though it hurts readability.

1
2
3
4
5
6
7
8
#include <iostream>
int n = 1; // global
int main() {
int n = 2; // shadows the global
{ int n = 3; } // block scope, destroyed on exit
std::cout << n << '\n'; // 2
std::cout << ::n << '\n'; // 1, explicit global access
}

Literal suffixes

Suffixes fix literal types: LL for long long, f for float, s for std::string. The 0b binary prefix and ' digit separator boost readability.

1
2
3
4
5
int a = 0b1010; // binary (C++14)
int b = 1'000'000; // digit separator
long long c = 5LL;
double d = 3.14f; // float
auto s = "hi"s; // std::string (with using std::string_literals)

C++-style casts

static_cast for compile-time semantic conversions; dynamic_cast for runtime polymorphic casts (pointer failure returns nullptr, reference throws bad_cast); const_cast drops const but only when the original object is non-const.

1
2
3
4
5
6
double d = 3.7;
int a = static_cast<int>(d); // semantic conversion
const double cd = 3.7;
double& ref = const_cast<double&>(cd); // strip const (use with care)
// dynamic_cast: downcast for polymorphic types
Derived* dp = dynamic_cast<Derived*>(base_ptr);

3.Data Types

Built-in types, enums, structs, template types, and modern utility types like std::optional, std::variant, and std::any.

Built-in types

Built-in types are distinguished by size and signedness. <cstdint> provides fixed-width types like int32_t/uint64_t with consistent cross-platform behavior.

1
2
3
4
5
6
7
#include <cstdint>
int32_t i = -1; // fixed-width signed integer
uint64_t u = 0; // fixed-width unsigned integer
float f = 1.5f; // 32-bit float
double d = 2.5; // 64-bit float
bool b = true;
char c = 'A';

enum class

enum class is safer than a plain enum: values must be qualified, and there is no implicit conversion to int — preventing name leakage and accidental integer use.

1
2
3
4
enum class Color { Red, Green, Blue };
Color c = Color::Red; // must be scope-qualified
int n = static_cast<int>(c); // 0, explicit conversion
enum class Flag : uint8_t { A = 1, B = 2 }; // specify underlying type

struct

struct defaults members to public and is great for passive data bundles. With default member initializers, brace-init creates a fully set-up object in one line.

1
2
3
4
5
struct Point {
double x = 0.0; // default member initializer
double y = 0.0;
};
Point p{1.0, 2.0}; // aggregate initialization

union

union shares one block of memory among several members, holding only one at a time. In standard C++ prefer std::variant; union is mostly for low-level memory reuse.

1
2
3
4
5
6
union Value {
int i;
double d; // same memory, only one member at a time
};
Value v;
v.d = 3.14; // writing d invalidates i's old value

Template types

Templates parameterize types; the compiler instantiates a version per used type — the foundation of generic programming and the standard containers.

1
2
3
4
5
template <typename T> struct Box {
T value;
};
Box<int> b1{42};
Box<double> b2{3.14}; // different instantiations of the same template

Type aliases

using declarations for type aliases are clearer than typedef and support template aliases. An alias doesn't create a new type — just another name for the same type.

1
2
3
4
5
using Id = unsigned long; // type alias
using PairInt = std::pair<int, int>;
template <typename T>
using Ptr = std::shared_ptr<T>; // template alias
Ptr<int> p = std::make_shared<int>(1);

optional

std::optional represents "may or may not have a value" returns, replacing sentinels or out-parameters. Use it in a boolean context to test presence; * extracts the value.

1
2
3
4
5
6
7
8
#include <optional>
std::optional<int> div(int a, int b) {
if (b == 0) return std::nullopt; // no value
return a / b;
}
if (auto v = div(6, 2)) {
int n = *v; // unpack
}

variant

std::variant is a type-safe union holding exactly one of a fixed list of types. std::get<T> returns by value (throws bad_variant_access on type mismatch), std::get_if<T> returns a pointer without throwing.

1
2
3
4
5
#include <variant>
std::variant<int, double, std::string> v;
v = 3.14; // currently holds a double
double d = std::get<double>(v);
if (auto* s = std::get_if<std::string>(&v)) { } // safe access

std::any

std::any holds any copyable type and can be cast back at runtime — handy for heterogeneous containers or plugin arguments, but it has type-erasure cost; prefer variant when possible.

1
2
3
#include <any>
std::any a = 42;
int n = std::any_cast<int>(a); // throws bad_any_cast on type mismatch

pair and tuple

pair holds two values, tuple any number. Structured bindings unpack elements into named variables — far more readable than indexing std::get.

1
2
3
4
5
std::pair<int, int> p{1, 2};
auto [a, b] = p; // unpack
std::tuple<int, double, char> t{1, 2.5, 'x'};
auto [i, d, c] = t;
std::get<1>(t); // index access, gets 2.5

4.Pointers and Arrays

Raw pointers, smart pointers, arrays, and references — who owns the memory and which to use when.

Raw pointer

A raw pointer stores an object's address: * dereferences, & takes the address. Raw pointers don't own memory; pair them with new/delete or hand them to a smart pointer.

1
2
3
4
5
int x = 10;
int* p = &x; // address of x
*p = 20; // modify x via the pointer
if (p == nullptr) { } // null check
int* q = nullptr; // null pointer

Pointer and array

An array name decays to a pointer to its first element in expressions: p+i is address arithmetic, *(p+i) is dereference. This is C-inherited low-level behavior.

1
2
3
4
int arr[4] = {1, 2, 3, 4};
int* p = arr; // array name decays to pointer to first element
std::cout << *(p + 2); // 3, pointer arithmetic
// arr[i] is equivalent to *(arr + i)

nullptr

nullptr is a type-safe null pointer constant, replacing NULL or 0. Dereferencing a null pointer is undefined behavior — always check before use.

1
2
3
4
5
int* p = nullptr;
if (p == nullptr) { /* safe branch */ }
void f(int* p) {
if (!p) return; // null-check before use
}

new and delete

new allocates heap memory and returns a pointer; delete frees it; new[] must pair with delete[]. Manual management leaks easily — prefer RAII containers or smart pointers.

1
2
3
4
int* p = new int(5); // dynamic single allocation
delete p; // free
int* arr = new int[10]; // dynamic array
delete[] arr; // must pair with []

unique_ptr

unique_ptr has exclusive ownership and deletes automatically at scope exit. make_unique is exception-safe; ownership is transferred via std::move; copying is forbidden.

1
2
3
4
5
#include <memory>
std::unique_ptr<int> p = std::make_unique<int>(42);
*p = 43; // use like a raw pointer
// p frees itself on scope exit; no delete needed
std::unique_ptr<int> q = std::move(p); // transfer ownership

shared_ptr

shared_ptr shares ownership via a reference count; the object is freed when the count hits zero. Avoid cycles — use weak_ptr to break them.

1
2
3
4
std::shared_ptr<int> a = std::make_shared<int>(10);
std::shared_ptr<int> b = a; // reference count +1
// freed automatically when the last reference goes out of scope
long use = a.use_count(); // 2

weak_ptr

weak_ptr doesn't own the object and doesn't affect the count; lock() temporarily promotes it to shared_ptr (empty if the object was already freed). Ideal for breaking cycles.

1
2
3
4
5
std::shared_ptr<int> sp = std::make_shared<int>(1);
std::weak_ptr<int> wp = sp; // does not increase the count
if (auto lk = wp.lock()) { // promote to shared_ptr
// use lk; lock() returns null when sp has been freed
}

Reference vs pointer

References are syntactically lighter and guaranteed non-null — prefer them for parameters; use a pointer when the argument might be absent or might be reseated.

1
2
3
4
5
void inc(int& v) { ++v; } // reference: non-null, direct
void inc2(int* v) { if (v) ++*v; } // pointer: may be null
int x = 1;
inc(x); // x becomes 2
inc2(&x); // x becomes 3

const pointers

Read right-to-left: int* const is a const pointer to int; const int* is a pointer to const int. Mixing them up is a classic declaration mistake.

1
2
3
4
int x = 1, y = 2;
int* const p = &x; // const pointer, cannot reseat
const int* q = &x; // pointer to const, cannot modify value
const int* const r = &x; // both const

void*

void* is a pointer to memory of unknown type; you must cast back to a concrete type before dereferencing. C APIs use it for opaque data; avoid it in C++.

1
2
3
void* raw = malloc(64); // opaque memory
int* p = static_cast<int*>(raw); // explicit conversion required
free(raw);

5.Control Flow

if/for/while/switch and the init-statement form of if/switch introduced in C++17.

if / else

if/else selects an execution path by condition. From C++17 on, if can carry an init-statement (see if-with-initializer).

1
2
3
4
5
6
7
if (n > 0) {
// positive branch
} else if (n == 0) {
// zero branch
} else {
// negative branch
}

for loop

The classic for loop has init / condition / step. Prefer ++i in counting loops — semantically it avoids one temporary copy.

1
2
3
4
for (int i = 0; i < 5; ++i) {
// 0..4
}
for (int i = 4; i >= 0; --i) { } // descending

range-for

Range-for (C++11) iterates container elements and avoids manual index and bounds bugs. Use auto& to modify, const auto& to read.

1
2
3
4
std::vector<int> v{1, 2, 3};
for (const auto& x : v) { } // read-only
for (auto& x : v) { ++x; } // modify elements
for (const auto& [k, val] : m) { } // unpack map

while / do-while

while checks the condition first; do-while runs the body at least once. Both fit cases where the iteration count is unknown and depends on runtime state.

1
2
3
int i = 0;
while (i < 3) { ++i; } // test first, then execute
do { ++i; } while (i < 3); // execute first, test after; runs at least once

switch / case

switch dispatches on an integer or enum value and reads more clearly than an if-chain. Every case must break (or return) or control falls through to the next label.

1
2
3
4
5
switch (cmd) {
case 1: run(); break;
case 2: run2(); break;
default: help(); break; // fallback
}

break and continue

continue skips the rest of the current iteration; break exits the entire loop. In nested loops, a label can let goto jump out of multiple levels.

1
2
3
4
5
for (int i = 0; i < 10; ++i) {
if (i % 2) continue; // skip odd numbers
if (i > 6) break; // terminate loop
// only handles 0,2,4,6
}

if with initializer

From C++17 on, if/switch can declare an init variable scoped to the branch, avoiding leakage into the enclosing scope — the idiomatic find-then-check form.

1
2
3
4
5
6
if (auto it = m.find("key"); it != m.end()) {
// it is scoped to the if block
}
switch (int v = parse(); v) {
case 0: break;
}

Ternary operator

The ternary operator expresses if/else assignment in one line. Both branches must be compatible types; for complex logic a plain if is clearer.

1
2
int max = (a > b) ? a : b; // condition ? true-value : false-value
std::string s = ok ? "yes" : "no";

goto label

goto jumps directly to a label, breaking structured control flow — almost always replaceable by a flag or return. The only defensible use is breaking out of nested loops.

1
2
3
4
5
6
7
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (bad(i, j)) goto done; // break out of both loops
}
}
done:
// only for breaking out of nested loops; don't overuse

6.Functions and Lambdas

Declarations and definitions, overloading, parameter passing, lambdas and function templates, variadic templates, and recursion.

Declare and define

A declaration tells the compiler the signature; a definition provides the body. Declarations can appear in headers many times; definitions must be unique per program.

1
2
3
4
5
6
// declaration (prototype): parameter types are the interface
int add(int a, int b);
// definition: implementation
int add(int a, int b) {
return a + b;
}

Function overloading

Overloading lets the same name dispatch by argument type; the compiler picks the best match. Return type alone cannot distinguish overloads.

1
2
3
4
void print(int v) { }
void print(const std::string& s) { }
print(1); // picks the int overload
print("hi"); // picks the string overload

Default arguments

Default arguments let the caller omit trailing arguments. They must be supplied contiguously from the right; provide them in only one of the declaration or definition.

1
2
3
void greet(const std::string& name, int times = 1) { }
greet("hi"); // times=1
greet("hi", 3); // times=3

Parameter passing

Pass-by-value copies the whole object; const& avoids the copy and forbids mutation; & allows modification; && (rvalue reference) receives temporaries so resources can be moved out.

1
2
3
4
void read(const std::string& s); // read-only: const& avoids copies
void write(std::string& s); // modify the original
void copy(std::string s); // needs a copy
void own(std::string&& s); // transfer ownership

Return value

Modern C++ returns temporaries via RVO/NRVO for zero copies. Never return the address or reference of a local — that's a dangling reference.

1
2
3
4
5
std::string make() { return "abc"; } // NRVO eliminates the copy
auto [a, b] = std::pair<int, int>{1, 2}; // unpack a returned pair
int& at(std::vector<int>& v, size_t i) {
return v[i]; // return by reference
}

Lambda expressions

A lambda is an anonymous function object: capture list [] + parameters + body. Combined with <algorithm> it's the mainstream way to write callbacks and comparators.

1
2
3
4
auto add = [](int a, int b) { return a + b; };
int r = add(1, 2);
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; }); // descending comparator

Lambda captures

[=] captures everything by value, [&] by reference, [x] captures specific names. When capturing by reference, make sure the original outlives the lambda.

1
2
3
4
5
int base = 10;
auto f = [base](int x) { return base + x; }; // by value
auto g = [&base](int x) { return base + x; }; // by reference
auto h = [=]() {}; // all by value
auto k = [&]() {}; // all by reference

Function templates

Function templates are instantiated per argument type deduced at the call site — one implementation serves all types. Types must support the operators used.

1
2
3
4
template <typename T>
T max_of(T a, T b) { return (a > b) ? a : b; }
int m = max_of(3, 5); // deduced as int
double d = max_of(2.5, 1.5); // deduced as double

Variadic templates

A parameter pack ...Args accepts any number of arguments; a fold expression (args + ...) applies a binary operator across the pack — a modern replacement for printf-style variadic functions.

1
2
3
4
5
template <typename... Args>
int sum(Args... args) {
return (args + ...); // C++17 fold expression
}
int total = sum(1, 2, 3, 4); // 10

Recursion

Recursion is a function calling itself; a base case terminates it. Deep recursion eats the call stack — watch for overflow and overlapping subproblems.

1
2
3
4
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}

7.Strings

std::string basics, concatenation, search/replace, formatting, string streams, and number/string conversions.

std::string basics

std::string is a mutable, growable character container with automatic memory management. Indexing via [] past the end is undefined; at() throws instead.

1
2
3
4
5
6
#include <string>
std::string s = "hello";
s += " world"; // concatenation
size_t len = s.size(); // length
char c = s[0]; // index access
bool empty = s.empty();

String literals

A "..." literal defaults to const char*; the s suffix gives a std::string; string_view is a read-only non-owning view, perfect for parameter passing without copies.

1
2
3
4
auto a = "plain"; // const char*
auto b = "u8"s; // std::string (with using std::string_literals)
std::string_view v = "view"; // read-only view
std::string_view::size_type n = v.size();

Concatenation and reserve

For heavy concatenation, reserve up front to avoid repeated reallocation. Chaining + creates temporaries — in hot paths, reuse a buffer.

1
2
3
4
5
6
std::string s = "a";
s.append("b").append("c"); // chained append
s.push_back('d');
s += 'e';
std::string out;
out.reserve(a.size() + b.size()); // pre-allocate

Substring and search

substr(pos, len) extracts a substring; find/rfind locate the first match, returning std::string::npos when not found.

1
2
3
4
5
6
std::string s = "hello world";
s.substr(6, 5); // "world"
s.find("world"); // 6
s.find('o'); // first 'o'
s.rfind('o'); // search from the right
if (s.find("x") == std::string::npos) { }

Replace

Member replace(pos, len, str) replaces by position; <algorithm>'s std::replace swaps every matching character by value.

1
2
3
std::string s = "a-b-c";
s.replace(1, 1, "+"); // replace 1 char starting at position 1
std::replace(s.begin(), s.end(), '-', '_'); // bulk replace

Formatted output

std::format (C++20) formats via placeholders with compile-time argument count checks — replacing sprintf and cout concatenation. :04d zero-pads; :.2f keeps two decimals.

1
2
3
4
#include <format> // C++20
std::string s = std::format("{}-{:04d}", 42, 7);
double d = std::format("{:.2f}", 3.14159);
// s = "42-0007",d = "3.14"

stringstream

stringstream treats an in-memory string as a stream: ostringstream builds, istringstream parses with >> — a flexible tool for formatting and reverse-parsing.

1
2
3
4
5
6
#include <sstream>
std::ostringstream oss;
oss << "x=" << 42 << " y=" << 3.5;
std::string s = oss.str();
std::istringstream iss("10 20");
int a, b; iss >> a >> b;

String/number conversion

stoi/stod/stoll parse strings into numbers; std::to_string converts back. Failures and overflows throw std::invalid_argument / out_of_range.

1
2
3
4
int i = std::stoi("42"); // to int
int j = std::stoi("ff", nullptr, 16); // hexadecimal
double d = std::stod("3.14");
std::string s = std::to_string(42); // "42"

Raw string literals

Inside R"(...)" the backslash is literal — perfect for regexes, paths, and multi-line text. If the body contains )", use a custom delimiter: R"tag(...)tag".

1
2
3
std::string re = R"(\w+@\w+\.\w+)"; // regex without escaping
std::string path = R"(C:\tmp\file)"; // backslash kept literally
std::string tag = R"tag(<b>bold</b>)tag"; // custom delimiter

Iterate characters

Range-for is the cleanest way to walk characters one by one; use char& to mutate. The begin/end iterator form plays nicely with legacy code and the algorithm library.

1
2
3
for (char c : s) { } // char by char (by value)
for (char& c : s) { c = 'x'; } // char by char (mutable)
for (auto it = s.begin(); it != s.end(); ++it) { }

8.Containers and Algorithms

vector/array/map/set, priority queues, plus sorting, searching, and ranges algorithms.

vector

vector is a dynamic array: O(1) push/pop at the back, O(1) random access. If you push_back a lot, reserve first; avoid front insertion (O(n)).

1
2
3
4
5
6
std::vector<int> v;
v.push_back(1); // append at the back
v.emplace_back(2); // construct in place
v.pop_back(); // remove the back
int x = v[0]; // out-of-bounds is UB; at() throws
v.reserve(100); // pre-allocate

std::array

std::array is the modern wrapper for a fixed-size stack array: it has size()/begin()/end() and plays well with the algorithm library. Use array for fixed size, vector for variable size.

1
2
3
std::array<int, 4> a{1, 2, 3, 4}; // fixed size on the stack
int n = a.size(); // known at compile time
std::sort(a.begin(), a.end());

map

std::map is an ordered key/value store (red-black tree): keys stay sorted, lookup is O(log n). When order doesn't matter, use unordered_map (average O(1)).

1
2
3
4
5
std::map<std::string, int> m;
m["a"] = 1; // overwrite if the key exists
m.insert_or_assign("b", 2); // C++17
auto it = m.find("a");
if (it != m.end()) { } // check existence

unordered_map

unordered_map is a hash table with average O(1) lookup and no ordering. operator[] inserts a default value for missing keys — for pure queries use find or at().

1
2
3
4
5
#include <unordered_map>
std::unordered_map<int, int> m;
m[1] = 10; // hash bucket lookup
int v = m[1]; // missing keys insert a default value
if (m.find(1) != m.end()) { }

set

set holds an ordered, deduplicated collection; insert/erase/find are all O(log n); unordered_set is the hash version (unordered, O(1)). contains() (C++20) tests membership.

1
2
3
4
5
std::set<int> s{3, 1, 2};
s.insert(4); // auto dedup + sort
s.erase(1);
if (s.contains(2)) { } // C++20 membership check
std::unordered_set<int> us; // hash variant

deque and list

deque gives O(1) insertion/removal at both ends; list gives O(1) middle insertion but O(n) random access. Pick the container that matches your access pattern.

1
2
3
4
5
6
std::deque<int> dq; // O(1) insert/remove at both ends
dq.push_front(1);
dq.push_back(2);
std::list<int> lst; // doubly linked list
lst.push_back(3);
lst.insert(lst.begin(), 0); // O(1) middle insertion

priority_queue (heap)

priority_queue is a heap: push is O(log n), top is O(1). Default is max-heap; for min-heap supply a greater comparator.

1
2
3
4
5
6
#include <queue>
std::priority_queue<int> pq; // default max-heap
pq.push(3); pq.push(1); pq.push(2);
int top = pq.top(); // 3
pq.pop();
// min-heap: priority_queue<int, vector<int>, greater<int>>

Sort

std::sort sorts a random-access container in-place in O(n log n). A custom comparator returns whether a should come before b. For list, use its member sort().

1
2
3
4
std::sort(v.begin(), v.end()); // ascending
std::sort(v.begin(), v.end(), std::greater<int>()); // descending
std::sort(v.begin(), v.end(),
[](const Item& a, const Item& b) { return a.price < b.price; });

Search

Use std::find for linear lookup on unsorted containers; binary_search/lower_bound for O(log n) on sorted ones. find returns an iterator; compare against end() to detect a hit.

1
2
3
4
5
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) { }
std::sort(v.begin(), v.end()); // sort before binary search
auto lo = std::lower_bound(v.begin(), v.end(), 50);
bool ok = std::binary_search(v.begin(), v.end(), 42);

ranges pipeline

C++20 ranges compose lazy views via |: filter selects, transform maps, with no copies or allocations — sequence processing as an expression.

1
2
3
4
5
#include <ranges>
std::vector<int> v{1, 2, 3, 4, 5};
auto even = v | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
for (int x : even) { } // 4, 16

9.Dynamic Memory and Ownership

RAII, move semantics, smart pointer trade-offs, and exception safety — the core of C++ resource management.

RAII

RAII ties resource acquisition to the constructor and release to the destructor — resources are freed automatically on destruction, with no manual cleanup and full exception safety.

1
2
3
4
5
6
7
8
// RAII: tie resource lifetime to object lifetime
class File {
FILE* f_;
public:
File(const char* path) { f_ = fopen(path, "r"); }
~File() { if (f_) fclose(f_); } // destructor releases automatically
};
{ File f("a.txt"); } // closed automatically on scope exit

make_unique

make_unique builds a unique_ptr in one step that fuses new and construction — even if construction throws, no raw pointer leaks. Array form: make_unique<T[]>(n).

1
2
3
auto p = std::make_unique<Widget>(args...); // exception-safe
auto q = std::make_unique<int[]>(10); // dynamic array
// equivalent to new Widget(args...) but safer

make_shared

make_shared allocates the object and the control block together — one allocation instead of two. From C++20 the control block doesn't waste any size alignment.

1
2
3
std::shared_ptr<Big> sp = std::make_shared<Big>();
// one allocation holds both the object and the control block
std::weak_ptr<Big> wp = sp; // pair with a weak reference

Move semantics

std::move casts an lvalue to an rvalue reference, enabling move instead of copy — internal pointers transfer over, the source is left empty, no deep copy of large arrays.

1
2
3
std::vector<int> big(1000000);
auto moved = std::move(big); // transfer ownership; big becomes empty
// std::move doesn't move data, just marks big as "movable-from"

Move constructor

A move constructor steals the source's resources and leaves it empty — much faster than copy. Mark it noexcept so vector reallocation prefers move over copy.

1
2
3
4
5
struct Buffer {
int* data;
Buffer(Buffer&& other) noexcept
: data(other.data) { other.data = nullptr; }
};

Return value optimization

The compiler elides redundant copy/move constructions and builds the return value directly in the caller's storage. C++17 guarantees zero-copy for prvalue returns.

1
2
3
4
5
std::vector<int> make() {
std::vector<int> v(1000);
return v; // NRVO: construct directly in the destination
}
auto v = make(); // zero copies

Break cycles with weak_ptr

In parent/child trees, the parent holds shared_ptr while the child back-references the parent with weak_ptr — otherwise they keep each other alive forever. lock() safely promotes to shared_ptr.

1
2
3
4
5
6
7
8
9
10
struct Node;
struct Parent {
std::shared_ptr<Node> child;
};
struct Node {
std::weak_ptr<Parent> parent; // back-pointer uses weak
void refresh() {
if (auto p = parent.lock()) { p->update(); }
}
};

noexcept

noexcept declares a function non-throwing — vector uses it to decide between move and copy during reallocation. If it throws anyway, std::terminate is called; only mark it when you're sure.

1
2
3
4
5
void parse() noexcept { } // promise: does not throw
std::vector<int> v(1000);
void run() noexcept {
v.push_back(1); // dangerous: push_back may throw bad_alloc
}

Manual leaks

Manual new/delete leaks if an exception fires or an early return happens. RAII (smart pointers / containers) hands cleanup to the destructor — exception-safe.

1
2
3
4
5
6
7
// BAD: an early return or exception leaks
int* p = new int(5);
use(p);
delete p;
// GOOD: RAII cleans up automatically
auto sp = std::make_unique<int>(5);
use(sp.get());

10.Object-Oriented Programming

Classes, access control, constructors and destructors, inheritance and polymorphism, pure-virtual interfaces, friend, and operator overloading.

Class definition

class bundles data with the operations on it. Members are private by default (the opposite of struct); access them via public methods.

1
2
3
4
5
6
7
8
9
class Account {
public:
void deposit(double n) { balance_ += n; }
double balance() const { return balance_; }
private:
double balance_ = 0;
};
Account a;
a.deposit(100);

Access specifiers

public: visible to everyone. protected: visible to the class and its derivatives. private: visible only to the class. Defaults are private for class, public for struct.

1
2
3
4
5
6
7
8
class Widget {
public: // accessible to anyone
int visible = 0;
protected: // accessible to self and derived classes
int semi = 0;
private: // accessible to self only
int hidden = 0;
};

Constructor

Constructors share the class name; use the initializer list : x(x_) for members (more efficient than assigning in the body). The parameterless one is the default constructor.

1
2
3
4
5
6
7
struct Vec2 {
double x, y;
Vec2() : x(0), y(0) {} // default constructor
Vec2(double x_, double y_) : x(x_), y(y_) {} // parameterized constructor
};
Vec2 a; // default
Vec2 b{1.0, 2.0}; // parameterized

Delegating constructor

A delegating constructor hands off to another constructor in the same class — no repeated initialization logic. The delegated target must already be declared.

1
2
3
4
5
6
class Point {
int x_, y_;
public:
Point() : Point(0, 0) {} // delegate to the one below
Point(int x, int y) : x_(x), y_(y) {} // target constructor
};

Destructor

The destructor runs on object teardown — it's where RAII releases resources. A base-class destructor must be virtual or deleting a derived object via base pointer only destroys the base part.

1
2
3
4
5
6
class ScopedLock {
std::mutex& m_;
public:
explicit ScopedLock(std::mutex& m) : m_(m) { m_.lock(); }
~ScopedLock() { m_.unlock(); } // auto-unlock
};

Inheritance

Inheritance lets a derived class reuse the base's interface and implementation. Calling a virtual function through a base pointer/reference triggers runtime polymorphism.

1
2
3
4
5
6
struct Animal { virtual void speak() const { } };
struct Dog : Animal {
void speak() const override { /* bark */ }
};
Animal* a = new Dog();
a->speak(); // polymorphism: calls the Dog version

virtual and abstract class

virtual marks overridable functions; pure-virtual (=0) makes the class abstract and uninstantiable. A class with any virtual function should also have a virtual destructor.

1
2
3
4
5
6
7
8
struct Shape {
virtual double area() const = 0; // pure virtual -> abstract class
virtual ~Shape() = default; // base class destructor must be virtual
};
struct Circle : Shape {
double r_;
double area() const override { return 3.14 * r_ * r_; }
};

override and final

override asserts "I'm overriding a base virtual" — a mismatched signature becomes a compile error instead of silently creating a new function. final forbids further overrides.

1
2
3
4
5
struct Base { virtual void f(int) { } };
struct Derived : Base {
void f(int) override; // explicitly declare override
void g() final; // forbid further overrides in derived classes
};

Pure-virtual interface

A pure-virtual function (= 0) defines an interface without an implementation; a class with one is abstract, so derived classes must implement it to be instantiable — much like a Java interface.

1
2
3
4
5
6
7
8
struct Logger {
virtual void log(const std::string&) = 0;
virtual ~Logger() = default;
};
struct ConsoleLogger : Logger {
void log(const std::string& msg) override { }
};
std::unique_ptr<Logger> l = std::make_unique<ConsoleLogger>();

friend

friend grants specified classes or functions access to private members, breaking encapsulation. Use it sparingly, in tightly-coupled scenarios (operator overloading, internal iterators).

1
2
3
4
5
class Secret {
int code_ = 42;
friend class Reveal; // Reveal can access private members
friend int read(const Secret&);
};

Operator overloading

Operator overloading lets your types support +, -, <<, etc. Keep semantics intuitive — + shouldn't mutate operands, << is for output — and don't overuse it, as readability suffers.

1
2
3
4
5
6
struct Money {
double v;
Money operator+(const Money& o) const { return {v + o.v}; }
};
Money a{1.5}, b{2.0};
Money c = a + b; // calls operator+

11.Error Handling

Exception catching, the standard exception hierarchy, custom exceptions, noexcept, and non-throwing paths via optional/expected.

try / catch

Exceptions thrown inside a try block are caught by a matching catch. Prefer catching std::exception& as the base, and split per concrete type when needed; uncaught exceptions unwind the call stack.

1
2
3
4
5
try {
risky();
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << '\n';
}

catch by reference

Catch by const& to preserve polymorphism with zero copies; catch by value and you'll slice (the derived part is cut off). catch(...) catches everything but loses type info.

1
2
3
4
5
// GOOD: catch by const& to avoid slicing
catch (const std::runtime_error& e) { }
// BAD: catch by value causes slicing
catch (std::runtime_error e) { }
// catch (...) catches everything, but gives no type info

Standard exception hierarchy

Standard exceptions derive from std::exception. Put specific catches first and the std::exception& fallback last — the most specific match wins.

1
2
3
4
catch (const std::bad_alloc& e) { } // allocation failure
catch (const std::out_of_range& e) { } // out of bounds
catch (const std::invalid_argument& e) { } // invalid argument
catch (const std::exception& e) { } // catch-all fallback

Custom exception

Custom exceptions derive from std::runtime_error (or a sibling); using inherits the constructors, what() carries the message, and a base-class catch in the caller is enough.

1
2
3
4
5
class MyError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
throw MyError("config missing");

noexcept and exceptions

noexcept promises no exceptions — vector reallocation and move operations rely on it. If it throws anyway, std::terminate ends the program; don't mark it when in doubt.

1
2
3
4
5
void parse() noexcept { } // promises not to throw
std::vector<int> v(1000); // vector operations may throw
void run() noexcept {
v.push_back(1); // dangerous: push_back may throw
}

optional for failure

For "expected may fail" operations, returning std::optional is lighter than throwing — callers explicitly handle the empty case. Reserve exceptions for truly unexpected errors.

1
2
3
4
5
std::optional<double> sqrt_opt(double x) {
if (x < 0) return std::nullopt;
return std::sqrt(x);
}
if (auto r = sqrt_opt(-1.0)) { } else { /* no-value branch */ }

std::expected

std::expected (C++23) carries either a value or an error description — richer than optional, more predictable than exceptions. A natural fit for parsing and validation code.

1
2
3
4
5
6
#include <expected> // C++23
std::expected<int, std::string> parse(const std::string& s) {
try { return std::stoi(s); }
catch (...) { return std::unexpected("bad number"); }
}
if (auto v = parse("42")) { int n = *v; }

Exception safety

Exception safety comes from RAII: cleanup belongs in local destructors so it runs on every exit path — normal return, exception, anything — preventing leaks and inconsistent state.

1
2
3
4
5
6
7
struct Guard {
~Guard() { /* rollback or cleanup */ }
};
void op() {
Guard g; // destructor runs on every exit path
do_something_that_may_throw();
}

errno with C

errno is a C-era global error code, overwritten by the next failed call and not thread-safe. In C++ prefer exceptions; read errno immediately when interacting with C functions.

1
2
3
if (fopen("a.txt", "r") == nullptr) {
std::cerr << "errno=" << errno << '\n'; // read it immediately
}

12.File and Stream I/O

Reading and writing files, line-by-line reading, formatted output, filesystem paths, and binary files.

Write file

ofstream opens a file for writing; << writes formatted output. The destructor closes it; an explicit close() surfaces write failures earlier. Default is to overwrite existing files.

1
2
3
4
#include <fstream>
std::ofstream out("out.txt");
out << "hello " << 42 << '\n'; // formatted write
out.close(); // explicit close

Read file

ifstream opens a file for reading; getline reads line by line. The loop condition is the return value of getline — it stops at EOF or on failure.

1
2
3
4
5
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) { // read line by line
// process line
}

Line vs word

getline reads an entire line; >> reads a single token and skips whitespace. Mixing them: after >> you must ignore() the leftover newline or the next getline returns an empty string.

1
2
3
4
5
std::string line;
std::getline(std::cin, line); // until newline
int n;
std::cin >> n; // skip whitespace, read one word
std::cin.ignore(); // discard trailing newline

Format control

<iomanip>'s setw / setprecision / fixed control output formatting — column alignment and decimal precision when printing tables.

1
2
3
4
#include <iomanip>
std::cout << std::setw(8) << std::left << "name";
std::cout << std::fixed << std::setprecision(2) << 3.14159;
// 3.14

In-memory stream to disk

ostringstream assembles the output in memory first, then a single write hits the disk — fewer I/O syscalls. For input, parse through istringstream first to validate.

1
2
3
std::ostringstream oss;
oss << "id=" << 7;
std::ofstream("log.txt") << oss.str();

filesystem directories

<filesystem> (C++17) handles paths and directories: exists / create_directories / iteration. More robust than string-concatenated paths, with automatic separator handling.

1
2
3
4
5
#include <filesystem>
namespace fs = std::filesystem;
fs::exists("a.txt");
fs::create_directories("out/sub"); // create directories recursively
for (auto& e : fs::directory_iterator(".")) { }

Path components

fs::path handles decomposition and joining with the platform-correct separator. filename / extension / parent_path extract the parts.

1
2
3
4
5
fs::path p = "dir/sub/file.txt";
p.filename(); // "file.txt"
p.extension(); // ".txt"
p.parent_path(); // "dir/sub"
p.replace_extension(".md");

Binary file

Binary mode std::ios::binary skips newline translation; write/read operate on raw byte blocks. When dumping structs, mind alignment and endianness for portability.

1
2
3
4
5
std::ofstream out("data.bin", std::ios::binary);
int v = 12345;
out.write(reinterpret_cast<const char*>(&v), sizeof(v));
std::ifstream in("data.bin", std::ios::binary);
in.read(reinterpret_cast<char*>(&v), sizeof(v));

Validate stdin

When cin >> fails, the stream enters the fail state and every subsequent read fails. Call clear() to reset state and ignore() to drop the bad input before continuing. Always validate cin after reading.

1
2
3
4
5
6
int n;
std::cin >> n;
if (!std::cin) {
std::cin.clear(); // clear the fail state
std::cin.ignore(); // discard bad data
}

13.Common Pitfalls

Ten classic C++ anti-patterns (BAD) with the correct alternative (GOOD) — color-coded for side-by-side comparison.

Signed/unsigned mix

Comparing signed with unsigned implicitly converts the signed operand to unsigned, turning -1 into a huge positive value. Cast explicitly or unify signedness before comparing.

1
2
3
4
5
// BAD: -1 gets converted to a huge unsigned number
int x = -1;
if (x < 0u) { /* always false */ }
// GOOD: explicitly cast to signed before comparing
if (static_cast<int>(0u) > x) { }

Dangling references

Returning a reference or pointer to a local is a dangling reference: the memory is gone after the function returns, and any use is undefined behavior. Return by value or extend the object's lifetime.

1
2
3
4
// BAD: returning a reference to a local
int& bad() { int x = 1; return x; }
// GOOD: return by value (RVO gives zero-copy)
int good() { int x = 1; return x; }

Non-virtual base destructor

Deleting a derived object through a base pointer requires a virtual base destructor — otherwise the derived subobject isn't destroyed, leaking its resources.

1
2
3
4
5
// BAD: derived destructor does not run
struct Base { ~Base() { } };
// GOOD: virtual guarantees full destruction
struct Base2 { virtual ~Base2() = default; };
delete base_ptr; // only the Base2 version is safe

Integer division

Dividing two ints yields an int (truncation) — assigning to a double afterwards doesn't recover the fraction. Cast at least one operand to a floating type before dividing.

1
2
3
4
5
6
// BAD: 2/3 evaluates to 0
int a = 2, b = 3;
double r = a / b; // 0.0
// GOOD: cast one operand to floating-point first
int n2 = static_cast<int>(a);
double r2 = static_cast<double>(a) / b; // 0.666...

String concat in loop

s = s + x copies the entire string each iteration into a fresh temporary — O(n²). reserve + += appends in place — O(n). For large inputs the difference is dramatic.

1
2
3
4
5
6
7
// BAD: keeps copying the whole string each iteration
std::string s;
for (int i = 0; i < 10000; ++i) s = s + "x";
// GOOD: reserve up front, then append in place
std::string t;
t.reserve(10000);
for (int i = 0; i < 10000; ++i) t += "x";

using namespace std

using namespace std in a header leaks every std symbol into everyone who includes it — guaranteed name clashes. Use precise using-declarations or qualify with std::.

1
2
3
4
5
// BAD: pollutes every includer
using namespace std;
// GOOD: import only what you need, and only in .cpp files
using std::string;
using std::cout;

i++ vs ++i

i++ returns the old value (a copy/temporary), ++i increments directly. For int it doesn't matter; for user-defined types and iterators, ++i avoids the extra copy.

1
2
3
4
// BAD: wasted copy when the old value is not needed
for (int i = 0; i < n; i++) { }
// GOOD: the difference is significant for iterators
for (int i = 0; i < n; ++i) { }

const correctness

Read-only parameters should be const& — self-documenting and copy-free. const correctness lets the compiler catch unintended writes and signals to callers that the argument isn't modified.

1
2
3
4
// BAD: read-only parameter missing const
void dump(std::string s);
// GOOD: const& makes the read-only intent explicit
void dump(const std::string& s);

Macros vs functions

Macros bypass type checking, ignore scope, and can re-evaluate their arguments. Prefer const/constexpr functions or templates whenever you can.

1
2
3
4
// BAD: macros have no type checking
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// GOOD: constexpr functions are type-checked
constexpr int max_of(int a, int b) { return a > b ? a : b; }

Copy in range-for

Range-for by value copies every element. For large objects, use const auto& for read-only access or auto& for mutation. By-value is only right when items are small and a copy is desired.

1
2
3
4
// BAD: copies each element
for (auto item : items) { use(item); }
// GOOD: use a const reference for read-only access
for (const auto& item : items) { use(item); }

14.Threads and Concurrency

std::thread, mutexes, atomics, condition variables, and async tasks.

Create a thread

std::thread spawns a new thread to run a callable (function or lambda). The thread object must be joined or detached before destruction or std::terminate fires.

1
2
3
4
5
6
#include <thread>
std::thread t([] {
// new thread body
std::cout << "hello from thread\n";
});
// must call join() or detach()

join and detach

join blocks the current thread until the child finishes; detach lets the child run independently — after detaching you can't join, and any objects it accesses must outlive it.

1
2
3
4
5
std::thread t(worker);
t.join(); // block until the thread finishes
// or
// t.detach(); // detach, runs in the background
if (t.joinable()) t.join(); // check joinable() before joining

mutex

mutex protects shared data by allowing only one holder at a time. Manual lock/unlock is easy to forget on exception paths — prefer lock_guard or unique_lock.

1
2
3
4
5
6
#include <mutex>
std::mutex m;
int shared = 0;
m.lock();
++shared;
m.unlock(); // skipping this on exception will deadlock

lock_guard

lock_guard manages a mutex via RAII: locks on construction, unlocks on destruction. Every exit path — including exceptions — releases the lock. The standard way to lock.

1
2
3
4
5
std::mutex m;
{
std::lock_guard<std::mutex> g(m); // locks on construction
++shared; // critical section
} // unlocks on destruction

atomics

std::atomic offers lock-free atomic operations on fundamental types. fetch_add / load / store are all atomic and far cheaper than a mutex for simple counters.

1
2
3
4
5
#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1); // atomic increment
int cur = counter.load(); // atomic load
counter.store(42); // atomic store

condition_variable

condition_variable lets threads wait on a condition: wait releases the lock and blocks; notify wakes it. Always pass a predicate to wait to guard against spurious wake-ups.

1
2
3
4
5
6
7
8
9
std::mutex m;
std::condition_variable cv;
bool ready = false;
// producer
{ std::lock_guard<std::mutex> g(m); ready = true; }
cv.notify_one();
// consumer
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return ready; });

async / future

std::async launches an async task and returns a future; get() blocks for the result. Simpler than hand-rolling thread + shared state; pass std::launch::async to force a new thread.

1
2
3
4
#include <future>
auto fut = std::async(std::launch::async,
[] { return compute(); });
int result = fut.get(); // block until the result is ready

thread_local

thread_local gives each thread its own independent copy — a natural fit for lock-free caches, counters, or scratch state. Destroyed when the thread exits.

1
2
3
4
5
thread_local int cache = 0; // independent copy per thread
int get() {
if (!cache) cache = compute(); // computed once per thread
return cache;
}

Data races

Two threads reading and writing the same non-atomic variable is a data race — undefined behavior with unpredictable results. Synchronize with atomic or mutex.

1
2
3
4
5
6
7
// BAD: concurrent read/write on a non-atomic variable
int shared = 0;
// thread A: ++shared thread B: ++shared
// -> the result may be 1 instead of 2
// GOOD: protect with an atomic or a mutex
std::atomic<int> safe{0};
safe.fetch_add(1);

Parallel algorithms

C++17 <execution> adds execution policies to algorithms: par for parallelism, unseq for vectorization. On large datasets it uses all cores — provided elements share no mutable state.

1
2
3
4
5
#include <execution>
std::vector<int> v(1000);
std::sort(std::execution::par, v.begin(), v.end());
std::transform(std::execution::par_unseq,
v.begin(), v.end(), v.begin(), [](int x) { return x * 2; });

15.Networking (Sockets)

POSIX socket creation, listen, connect, send/recv, and timeouts (for cross-platform networking prefer Boost.Asio or libcurl).

Create a socket

POSIX sockets are the bedrock of network I/O: socket() creates one — AF_INET for IPv4, SOCK_STREAM for TCP. The return is a file descriptor; -1 indicates failure.

1
2
3
4
#include <sys/socket.h>
#include <netinet/in.h>
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { /* errno explains why */ }

bind and listen

bind attaches the socket to a port; listen starts accepting connections. htons converts host-byte-order to network-byte-order. sockaddr_in holds an IPv4 address.

1
2
3
4
5
6
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY; // bind to all network interfaces
bind(fd, (sockaddr*)&addr, sizeof(addr));
listen(fd, 16); // listen backlog

accept

accept pulls the next client connection off the queue and returns a new socket — one fd per connection. The original listening fd keeps accepting further connections.

1
2
3
4
5
6
while (true) {
int client = accept(fd, nullptr, nullptr);
if (client < 0) continue;
handle(client); // new connection
close(client);
}

Client connect

On the client, socket() + connect() reach the server. inet_pton converts a dotted-decimal IP to binary. connect returns -1 on failure.

1
2
3
4
5
6
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
connect(fd, (sockaddr*)&addr, sizeof(addr));

send and recv

send/recv exchange bytes over a TCP socket. recv returns 0 when the peer closes, -1 on error. TCP is a byte stream — frame the messages yourself.

1
2
3
4
5
std::string msg = "hello";
send(fd, msg.data(), msg.size(), 0);
char buf[1024];
int n = recv(fd, buf, sizeof(buf), 0);
// n == 0 peer closed; n < 0 error

Hostname resolution

getaddrinfo resolves a hostname + service into a list of addresses you can connect to — handling IPv4/IPv6 for you. The recommended replacement for hand-written inet_pton + port.

1
2
3
4
5
#include <netdb.h>
struct addrinfo* res;
getaddrinfo("example.com", "80", nullptr, &res);
// try connecting to each entry in the res linked list
freeaddrinfo(res);

Timeouts

SO_RCVTIMEO sets a recv timeout — on expiry recv returns -1 with errno=EWOULDBLOCK. A blocking recv becomes controllable and won't hang forever.

1
2
3
4
#include <sys/time.h>
struct timeval tv{3, 0}; // 3 seconds
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// recv taking longer than 3s -> -1, errno = EWOULDBLOCK

Minimal HTTP request

HTTP requests are a text protocol: request line + headers + blank line. The demo sends raw socket bytes; production code should use libcurl (handles redirects, TLS, compression).

1
2
3
4
5
6
std::string req =
"GET / HTTP/1.1\r\n"
"Host: example.com\r\n"
"Connection: close\r\n\r\n";
send(fd, req.data(), req.size(), 0);
// read response: status line + headers + body per Content-Length

16.Time and Date

chrono duration and time_point, the two clocks, formatting, sleeping, and elapsed-time measurement.

duration

duration represents a length of time with a typed unit — seconds / milliseconds / microseconds. Literal suffixes s / ms / us are intuitive; duration_cast converts across units.

1
2
3
4
5
#include <chrono>
using namespace std::chrono_literals;
auto d = 3s + 500ms; // 3.5 seconds
std::chrono::seconds s = 90s;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(s);

time_point

time_point is a moment on the timeline — a clock plus a duration offset. Add/subtract durations to move along the line; subtract two time_points to get a duration.

1
2
3
4
using namespace std::chrono;
auto now = system_clock::now(); // current moment
auto past = now - 1h; // one hour ago
auto elapsed = now - past; // a duration

system_clock

system_clock is the wall clock; it interchanges with time_t via to_time_t / from_time_t. Use it for calendar times and log timestamps. Affected by system clock changes.

1
2
3
auto tp = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(tp);
// t can be formatted with strftime / put_time

steady_clock

steady_clock is monotonic and immune to system clock changes — the right choice for timing (elapsed measurements, deadlines). Use system_clock only for human-readable times.

1
2
3
4
5
auto t0 = std::chrono::steady_clock::now();
// use steady_clock for elapsed-time measurement
work();
auto t1 = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0);

Format time

put_time formats a time_t into a local string using a strftime pattern. %Y-%m-%d %H:%M:%S is the most common. localtime returns a pointer into a static buffer — not thread-safe.

1
2
3
4
#include <iomanip>
auto tp = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(tp);
std::cout << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M:%S");

Thread sleep

sleep_for sleeps for a duration; sleep_until sleeps until a time_point. They block the current thread — sleeping the main thread freezes the UI, so use them only for background/test code.

1
2
3
4
5
#include <thread>
using namespace std::chrono_literals;
std::this_thread::sleep_for(500ms);
std::this_thread::sleep_until(
std::chrono::steady_clock::now() + 2s);

Measure elapsed

The timing idiom: record a start, subtract from end to get a duration, duration_cast to the desired unit, then count() for the value. Use steady_clock to ignore system clock changes.

1
2
3
4
5
auto start = std::chrono::steady_clock::now();
heavy_work();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
std::cout << "elapsed " << ms << " ms\n";

C-style time conversion

time_t is a seconds-since-epoch timestamp; gmtime converts to UTC, localtime to local time, strftime formats the result. These functions share static buffers — not thread-safe.

1
2
3
4
5
std::time_t t = std::time(nullptr);
std::tm* utc = std::gmtime(&t);
std::tm* local = std::localtime(&t);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d", local);

Time zones

C++20's zoned_time renders a time with a time zone, handling daylight savings automatically. The pre-C++20 standard library has no time-zone support — fall back to localtime or a third-party library.

1
2
3
4
5
#include <chrono>
using namespace std::chrono;
auto tp = system_clock::now();
auto z = current_zone();
std::cout << zoned_time(z, tp) << '\n'; // print with time zone

17.Processes and Signals

system, fork/exec/wait for child processes, environment variables, signal handling, and piped output (POSIX concepts).

system

system() asks the shell to execute a string command — easy but unsafe (command injection, shell quirks). When you need to capture output or pass arguments, use fork + exec or popen.

1
2
3
#include <cstdlib>
int code = std::system("ls -l"); // run a shell command
// non-zero usually means failure; exact semantics are platform-specific

fork

fork duplicates the current process. It returns twice: the parent gets the child's pid, the child gets 0; -1 means failure. After fork both resume at the fork call.

1
2
3
4
5
6
7
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// child process
} else if (pid > 0) {
// parent process; pid is the child's PID
} else { /* fork failed */ }

exec

The exec family loads a new program into the current process, replacing it. Pair fork + exec to launch an external command. On success exec doesn't return; on failure it returns -1 — the child must handle that path.

1
2
3
4
5
pid_t pid = fork();
if (pid == 0) {
execl("/bin/echo", "echo", "hi", nullptr);
_exit(1); // only reached if exec fails
}

wait

waitpid waits for a specific child to exit and returns its status. WIFEXITED checks for normal exit; WEXITSTATUS extracts the code. A child you don't wait on becomes a zombie.

1
2
3
4
5
pid_t pid = fork();
if (pid == 0) { /* child does work */ _exit(0); }
int status;
waitpid(pid, &status, 0);
bool ok = WIFEXITED(status) && WEXITSTATUS(status) == 0;

Environment variables

getenv reads an env var (nullptr if absent), setenv writes it, unsetenv removes it. Environment variables are the simple config channel from parent to child processes.

1
2
3
4
#include <cstdlib>
const char* home = getenv("HOME"); // nullptr if not set
setenv("APP_ENV", "prod", 1); // 1 = overwrite existing value
unsetenv("APP_ENV");

signal

signal registers a handler for signals like SIGINT (Ctrl+C) and SIGTERM (the default for kill). Inside a handler do only async-signal-safe operations — no allocation, no I/O.

1
2
3
#include <csignal>
void handler(int sig) { /* keep it short; no heavy work */ }
std::signal(SIGINT, handler); // triggered by Ctrl+C

sigaction

sigaction is more reliable than signal: it supports a signal mask and flags. SA_RESTART automatically resumes blocking calls interrupted by a signal. Prefer sigaction in production.

1
2
3
4
5
struct sigaction sa{};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGTERM, &sa, nullptr);

exit vs _exit

exit terminates the process, runs atexit handlers, and flushes buffers. _exit terminates immediately with no cleanup. A forked child should _exit to avoid double-cleanup of the parent's resources.

1
2
3
exit(0); // runs atexit hooks, then exits
_exit(0); // immediate exit, no cleanup
return 0; // returning from main is equivalent to exit(0)

popen

popen runs a command and pipes to its output ("r" to read, "w" to write input) — unlike system, it can capture output. pclose closes the pipe and waits, returning the exit status.

1
2
3
4
FILE* p = popen("ls -l", "r");
char buf[256];
while (fgets(buf, sizeof(buf), p)) { /* process each line */ }
int code = pclose(p);

18.Regular Expressions

std::regex matching, search, replace, capture groups, flags, and raw string literals.

Basic match

std::regex defaults to the ECMAScript grammar. regex_match requires the whole string to match; regex_search finds a substring. R"()" raw strings keep regexes readable.

1
2
3
#include <regex>
std::regex re(R"(\d{3}-\d{4})"); // matches 123-4567
bool m = std::regex_match("123-4567", re); // true

Full match and groups

regex_search finds the first matching substring; smatch holds the full match (m[0]) and the capture groups (m[1]…). regex_match requires a full-string match — ideal for format validation.

1
2
3
4
5
6
7
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))");
std::smatch m;
std::string s = "date: 2026-08-02";
if (std::regex_search(s, m, re)) {
std::cout << m[0] << '\n'; // 2026-08-02
std::cout << m[1] << '\n'; // 2026, the first capture group
}

Iterate all matches

Loop regex_search starting after the previous match to walk all of them. m.suffix().first gives the starting point past the match — or use a regex_iterator directly.

1
2
3
4
5
6
7
8
std::regex re(R"(\d+)");
std::smatch m;
std::string s = "a1 b22 c333";
auto it = s.cbegin();
while (std::regex_search(it, s.cend(), m, re)) {
std::cout << m[0] << ' '; // 1 22 333
it = m.suffix().first;
}

Replace

regex_replace globally rewrites every match. In the replacement template $& is the whole match and $1 is the first capture group. For position-aware edits use a regex_iterator.

1
2
3
4
std::regex re(R"(\d+)");
std::string s = "v1.2.3";
std::string out = std::regex_replace(s, re, "[$&]");
// v[1].[2].[3]

sregex_iterator

sregex_iterator packages "find every match" into a real iterator — much cleaner than a hand-written while regex_search loop when you want to extract them all.

1
2
3
4
5
6
std::regex re(R"(\d+)");
std::string s = "a1 b22 c333";
std::sregex_iterator it(s.begin(), s.end(), re);
for (; it != std::sregex_iterator{}; ++it) {
std::cout << it->str() << ' '; // 1 22 333
}

Capture groups

Parentheses define capture groups; smatch indexes them by number. (?:...) is a non-capturing group — groups without numbering. Named groups (?<name>...) are accessed via m["name"].

1
2
3
4
5
6
std::regex re(R"((\w+)@(\w+)\.(\w+))");
std::smatch m;
if (std::regex_match("[email protected]", m, re)) {
std::cout << m[1] << ' ' << m[2] << '.' << m[3];
// a b.com
}

Match flags

The second argument of std::regex sets flags: icase for case-insensitive, multiline so ^/$ match line starts/ends, ECMAScript/extended to pick a grammar dialect. Combine with |.

1
2
3
std::regex re("hello", std::regex::icase); // ignore case
std::regex re2("^a.*b$", std::regex::multiline); // ^ and $ per line
std::regex re3("x+", std::regex::extended); // POSIX syntax

Raw string regex

Inside R"(...)" the backslash is literal — write regexes and paths without escape-counting. If the body contains )" use a custom delimiter: R"tag(...)tag". Always prefer raw string literals for regex.

1
2
3
4
// raw string literal: no more double-escaping \d
std::regex re(R"(\w+@\w+\.\w+)");
// the traditional form is easy to miscount
std::regex re2("\\w+@\\w+\\.\\w+");

regex_error

An invalid regex throws std::regex_error at construction — catch it so a bad pattern doesn't crash you. For expensive patterns reuse the regex object to skip recompilation.

1
2
3
4
5
try {
std::regex r("([unclosed"); // invalid regex
} catch (const std::regex_error& e) {
std::cerr << e.what() << '\n';
}

19.Build and Debug

Compiler flags, Makefile/CMake, formatters, sanitizers, debuggers, and profilers.

Common flags

Common flags: -std for the standard, -O for optimization, -g for debug info, -Wall -Wextra for warnings, -Werror to make warnings fatal. Multi-file: -c each source then link the objects.

1
2
3
// $ g++ -std=c++20 -O2 -Wall -Wextra -g main.cpp -o app
// -pthread links the threading library (required when using std::thread)
// $ g++ -c a.cpp -o a.o compile only, no link

Makefile

A Makefile declares targets and dependencies; make rebuilds only what's stale. $< is the first prerequisite, $@ is the target, $^ is all prerequisites. Recipe lines must start with a tab.

1
2
3
4
5
6
7
8
9
# Makefile
CXX = g++
CXXFLAGS = -std=c++20 -Wall -Wextra
app: main.o math.o
\t$(CXX) $^ -o $@
main.o: main.cpp math.h
\t$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
\trm -f app *.o

CMake

CMake is a declarative build generator — it produces Makefiles, VS projects, etc. target_link_libraries links libraries, find_package locates dependencies. Build in a separate build/ directory.

1
2
3
4
5
6
7
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(server LANGUAGES CXX)
add_executable(server main.cpp)
find_package(Threads REQUIRED)
target_link_libraries(server PRIVATE Threads::Threads)
# $ cmake -B build && cmake --build build -j

pkg-config

pkg-config queries a library's compile/link flags; $(pkg-config ...) injects them via shell substitution — the standard way to avoid hand-typed include paths and -l flags.

1
2
3
// $ pkg-config --cflags --libs openssl
// -I/usr/include/openssl -lssl -lcrypto
// $ g++ main.cpp $(pkg-config --cflags --libs openssl) -o app

clang-format

clang-format auto-formats code; commit a .clang-format file to lock the style. ColumnLimit sets the line width, SortIncludes orders headers. Run it before committing to stay consistent.

1
2
3
4
5
6
# .clang-format
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
SortIncludes: true
# $ clang-format -i src/*.cpp

Sanitizers

Compile-time sanitizers catch runtime memory and concurrency bugs: address for out-of-bounds/leaks, undefined for UB, thread for data races. Always-on for tests.

1
2
3
4
// $ g++ -fsanitize=address,undefined -g main.cpp -o app
// ASan: out-of-bounds / leaks / use-after-free
// $ g++ -fsanitize=thread -g main.cpp -o app
// TSan: data races

gdb

gdb is the command-line debugger; compile with -g to embed symbols. break sets a breakpoint, run launches, bt shows the stack, print inspects a value. VSCode/CLion debuggers are GUIs over gdb.

1
2
3
4
5
6
7
// $ g++ -g main.cpp -o app
// $ gdb ./app
// (gdb) break main
// (gdb) run
// (gdb) print x
// (gdb) bt
// (gdb) next / step

valgrind

valgrind finds runtime memory issues: uninitialized reads, out-of-bounds, leaks, double-frees. --leak-check=full prints a stack trace for every leak. It's slow — run it on a subset of tests.

1
2
3
// $ valgrind --leak-check=full ./app
// ==12345== 20 bytes in 1 blocks are definitely lost
// $ valgrind --tool=helgrind ./app // detect data races

Static library

ar packs multiple .o files into a static library lib*.a; -L points to a library directory and -l names the library (strip the lib prefix and .a suffix). Static archives get linked into the binary — no runtime dependency.

1
2
3
// $ ar rcs libmath.a math1.o math2.o
// $ g++ main.cpp -L. -lmath -o app
// runtime does not need libmath.a to be shipped separately

Profiling

gprof/perf profile hot functions. Build with g++ -pg to instrument, run the binary, then gprof reports call counts and time per function — that's where to optimize.

1
2
3
// $ g++ -pg main.cpp -o app
// $ ./app && gprof app gmon.out
// outputs each function's call count and time share

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This page is a self-contained C++17/C++20 cheatsheet covering the core language, the Standard Template Library (STL) and the build toolchain — roughly 80% of what you actually use in real projects. The emphasis is on modern idioms: smart pointers (unique_ptr / shared_ptr), move semantics, auto type deduction, range-based for, structured bindings, and C++20 concepts and ranges. C++ was created by Bjarne Stroustrup in 1985 and combines C's raw performance with high-level abstraction, which makes it a cornerstone language for systems programming, game engines and high-performance computing. Its 19 chapters each focus on a single topic: basic syntax, variables, types and pointers, control flow, functions, strings, collections, memory management, object-oriented programming, error handling, input and output, common pitfalls, concurrency, networking, time, processes, regular expressions and build tools. Every subsection pairs a short concept introduction with a copy-and-paste-ready code snippet, so it is easy to look things up and experiment. All code and text is rendered locally in your browser; no data ever leaves your device. For authoritative references, see cppreference and the ISO C++ standard drafts.

Version 2.1.0