C++ Cheat Sheet
Quick reference for modern C++: compiling, strings, STL containers and algorithms, lambdas, classes, smart pointers, templates and concepts. Targets C++20, with C++23 features marked "(C++23)".
On this page
Compile & Run #
| Command | What it does |
|---|---|
g++ -std=c++20 -Wall -Wextra main.cpp -o app | Compile with warnings as C++20 (clang++ takes the same flags) |
./app | Run it (Windows: app.exe) |
-std=c++23 | Use C++23 (older compilers: -std=c++2b) |
-g / -O2 | Debug info / optimize for speed |
g++ main.cpp util.cpp -o app | Compile and link several files |
-fsanitize=address,undefined | Catch memory errors and undefined behaviour at run time |
Basics & Output #
| Code | What it does |
|---|---|
#include <iostream> | Standard input/output streams |
std::cout << "x = " << x << '\n'; | Print (prefer '\n' over std::endl, which also flushes) |
std::cin >> n; | Read a value (stops at whitespace) |
std::getline(std::cin, line); | Read a whole line into a std::string |
std::cerr << "error\n"; | Print to the error stream |
std::format("{} + {} = {}", a, b, a + b) | Python-style formatting, returns a std::string (C++20, <format>) |
std::format("{:.2f}", 3.14159) | "3.14": 2 decimals |
std::format("{:>6}|{:<6}|{:^6}", a, b, c) | Right / left / centre align in 6 characters |
std::format("{:05} {:x} {:#b}", 42, 255, 5) | "00042 ff 0b101": zero-pad, hex, binary with prefix |
std::print("Hi {}\n", name); | Format and print directly (C++23, <print>; GCC 14+, recent Clang/MSVC) |
std::println("x = {}", x); | Same, adds a newline (C++23) |
References, const & auto #
| Code | What it does |
|---|---|
int& r = x; | Reference: another name for x, must be initialized, can't be reseated or null |
int* p = &x; | Pointer: can be nullptr and can point elsewhere later |
void inc(int& x) | Pass by reference: the function changes the caller's variable |
void show(const std::string& s) | Pass big objects by const& to avoid a copy |
const int max = 10; | Can't be changed after initialization |
constexpr int size = 16; | Compile-time constant |
constexpr int square(int x) { return x * x; } | Can be evaluated at compile time |
auto it = m.find(key); | Let the compiler deduce the type |
for (const auto& item : items) | Range-based for loop without copying |
for (auto& item : items) | Loop and modify the elements in place |
nullptr | Null pointer (don't use NULL or 0 in C++) |
std::string & std::string_view #
| Code | What it does |
|---|---|
std::string s = "hello"; | Owning, growable string (<string>) |
s += " world"; / s + t | Append / concatenate |
s.size() / s.empty() | Length / is it empty |
s.substr(pos, len) | Part of the string (a new copy) |
s.find("lo") | Position of the match, or std::string::npos |
s.starts_with("he") / s.ends_with("lo") | Prefix / suffix check (C++20) |
s.contains("ell") | Substring check (C++23) |
std::to_string(42) | Number to string |
std::stoi(s) / std::stod(s) | String to int / double (throws on bad input) |
void greet(std::string_view name) | Read-only view of any string, no copy (C++17) |
std::string(sv) | Make an owning copy from a string_view |
Containers #
| Code | What it does |
|---|---|
std::vector<int> v{1, 2, 3}; | Dynamic array, the default container |
v.push_back(4); / v.emplace_back(args) | Add to the end / construct in place |
v.pop_back(); / v.clear(); | Remove the last element / all elements |
v[i] / v.at(i) | Element access: unchecked / bounds-checked (throws std::out_of_range) |
v.front() / v.back() / v.size() | First / last element / count |
v.insert(v.begin() + i, x) / v.erase(v.begin() + i) | Insert / remove at position i |
std::erase_if(v, pred) | Remove all matching elements (C++20) |
std::array<int, 3> a{}; | Fixed-size array that knows its size |
std::deque<int> d; | Fast push_front / push_back at both ends |
std::map<std::string, int> m; | Sorted key-value pairs |
m["Ann"] = 30; | Insert or update (m[key] creates the key if missing) |
m.contains("Ann") | Key exists? (C++20; before: m.count(k) or m.find(k) != m.end()) |
m.erase("Ann"); | Remove a key |
std::unordered_map<std::string, int> h; | Hash map: faster lookups, no order |
std::set<int> s{3, 1, 3}; | Sorted unique values: {1, 3} (unordered_set = no order) |
Algorithms & Ranges #
| Code | What it does |
|---|---|
std::sort(v.begin(), v.end()); | Sort ascending (<algorithm>) |
std::sort(v.begin(), v.end(), std::greater<>()); | Sort descending |
std::ranges::sort(v); | Same, no iterator pair needed (C++20) |
std::find(v.begin(), v.end(), x) | Iterator to the first match, or v.end() |
std::count_if(v.begin(), v.end(), pred) | Count elements matching a predicate |
std::transform(v.begin(), v.end(), out.begin(), f) | Apply f to each element into out |
std::accumulate(v.begin(), v.end(), 0) | Sum (<numeric>; start value sets the type) |
std::max_element(v.begin(), v.end()) | Iterator to the largest element |
std::ranges::find(v, x) / std::ranges::count_if(v, pred) | Range versions of the classics (C++20) |
std::ranges::contains(v, x) | Does v contain x? (C++23) |
v | std::views::filter(pred) | std::views::transform(f) | Lazy pipeline over a range (C++20) |
std::views::iota(1, 11) | The numbers 1 to 10, lazily |
Lambdas #
| Code | What it does |
|---|---|
[](int x) { return x * 2; } | Lambda with no captures |
[x] | Capture x by value (a copy) |
[&total] | Capture total by reference (can change it) |
[=] / [&] | Capture everything used by value / by reference |
[this] | Capture the current object inside a member function |
[c = 0]() mutable { return ++c; } | Init-capture; mutable lets the copy change |
[](auto a, auto b) { return a + b; } | Generic lambda, works for any types |
auto cmp = [](const P& a, const P& b) { return a.age < b.age; }; | Store a lambda in a variable, e.g. for std::sort |
Classes #
class Account {
public:
explicit Account(std::string owner, double balance = 0)
: owner_{std::move(owner)}, balance_{balance} {} // member initializer list
double balance() const { return balance_; } // const: doesn't modify
void deposit(double amount);
private:
std::string owner_;
double balance_;
};
| Code | What it does |
|---|---|
explicit Account(...) | Constructor that can't be used for implicit conversions |
Account() = default; | Ask for the compiler-generated version |
Account(const Account&) = delete; | Forbid copying |
virtual double area() const = 0; | Pure virtual: makes the class abstract |
virtual ~Shape() = default; | Base classes used polymorphically need a virtual destructor |
double area() const override | Overrides a base virtual; compiler checks the signature |
class Circle final : public Shape | final: no class can inherit from it |
Vec2 operator+(const Vec2& o) const | Operator overloading |
bool operator==(const Vec2&) const = default; | Generated equality (C++20; <=> for all comparisons) |
friend std::ostream& operator<<(std::ostream&, const Vec2&) | Make std::cout << v work |
| Rule of 0 | Use std::string, std::vector, smart pointers so you need no destructor or copy/move code |
| Rule of 3 / 5 | Write a destructor? Then also copy constructor + copy assignment (+ move constructor + move assignment) |
Smart Pointers & Move Semantics #
| Code | What it does |
|---|---|
auto p = std::make_unique<Circle>(2.0); | Sole owner; deleted automatically (<memory>) |
std::unique_ptr<Shape> s = std::move(p); | Transfer ownership; p is now nullptr |
auto sp = std::make_shared<Account>("Ann"); | Shared ownership with a reference count |
sp.use_count() | How many shared_ptrs own the object |
std::weak_ptr<Node> parent; | Non-owning link to a shared_ptr (breaks cycles) |
p.get() / p.reset() | Raw pointer without giving up ownership / delete now |
std::vector<std::unique_ptr<Shape>> | Container of polymorphic objects |
std::string b = std::move(a); | Steal a's contents instead of copying; a is left valid but unspecified |
Buffer(Buffer&& other) noexcept | Move constructor; mark it noexcept so vector uses it |
void add(std::string name) + std::move(name) | Take by value, then move into place: cheap for temporaries |
Templates & Concepts #
template <typename T>
T maxOf(T a, T b) { return a > b ? a : b; }
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>; // C++20, <concepts>
template <Numeric T>
T sum(const std::vector<T>& v) { T s{}; for (const auto& x : v) s += x; return s; }
auto half(std::floating_point auto x) { return x / 2; } // abbreviated template (C++20)
| Code | What it does |
|---|---|
maxOf<double>(2, 2.5) | Specify the template argument explicitly |
template <typename T, std::size_t N> | Template with a value parameter (like std::array) |
requires std::totally_ordered<T> | Constrain a template with a requires clause (C++20) |
std::integral / std::floating_point / std::same_as<T, U> | Common standard concepts |
Exceptions #
| Code | What it does |
|---|---|
throw std::invalid_argument("amount must be positive"); | Throw an exception (<stdexcept>) |
try { ... } catch (const std::exception& e) { e.what(); } | Catch by const& and read the message |
catch (const std::out_of_range& e) | Catch a specific type; put specific catches before general ones |
catch (...) | Catch anything |
throw; | Re-throw the current exception inside a catch |
void f() noexcept | Promise not to throw (destructors and moves should be) |
Structured Bindings, optional & variant #
| Code | What it does |
|---|---|
auto [q, r] = std::pair{17 / 5, 17 % 5}; | Unpack a pair, tuple or struct (C++17) |
for (const auto& [key, value] : m) | Loop over a map with named parts |
if (auto it = m.find(k); it != m.end()) | if with an initializer (C++17) |
std::optional<int> parse(const std::string& s) | Maybe a value; return std::nullopt for "none" (<optional>) |
if (auto v = parse(s)) use(*v); | Check and use an optional |
opt.value_or(-1) | The value, or a default |
std::variant<int, std::string> v = "hi"; | Holds one of several types (<variant>) |
std::holds_alternative<int>(v) / std::get<int>(v) | Check the active type / read it (throws if wrong) |
std::visit([](const auto& x) { ... }, v); | Run code for whichever type is active |
Common Pitfalls #
| Mistake | What goes wrong |
|---|---|
std::vector<int> v{3}; vs v(3) | {3} is one element with value 3; (3) is three zeros |
for (int i = 0; i < v.size(); i++) | Signed/unsigned comparison; use range-for or std::size_t |
v.push_back(x) while iterating v | Invalidates iterators and references into the vector |
std::string_view sv = std::string("tmp"); | Dangling view: the temporary string is already destroyed |
| Missing virtual destructor | Deleting a derived object through a base pointer leaks or crashes |
Base b = derived; | Object slicing: the derived part is cut off; use references or pointers |
m[key] just to check a key | Inserts an empty entry; use contains or find |
new / delete by hand | Easy to leak; use std::make_unique or containers |
Using an object after std::move | It's in a valid but unspecified state; assign a new value first |
Tips #
- Compile with
-Wall -Wextraand during development with-g -fsanitize=address,undefined. - Default to
std::vector,std::stringandstd::unique_ptr; reach for other containers orshared_ptronly when you need their features. - Pass small types (
int,double,string_view) by value and big objects byconst&. - Prefer algorithms and range-for over hand-written index loops: fewer off-by-one bugs.
- Mark single-argument constructors
explicitand overriding functionsoverride; the compiler then catches mistakes for you.
Nothing on this sheet matches “”.