Complete Reference · File 03 of 05
C++ Programming

Advanced C++
Templates, STL & Modern C++

The deep end. Templates, the full STL, lambdas, smart pointers, move semantics, exception handling, file I/O, and every modern C++11/14/17/20 feature you need to write professional, production-grade C++. This file is where you stop writing code and start engineering software.

Ch 1 · Templates Ch 2 · STL Containers Ch 3 · STL Algorithms & Iterators Ch 4 · Smart Pointers Ch 5 · Lambdas & Functional Ch 6 · Exception Handling Ch 7 · File I/O & Streams Ch 8 · Modern C++11–20 Ch 9 · Concurrency Primer Ch 10 · Traps & Master Reference
Chapter 01
Templates — Generic Programming & Compile-Time Power

1.1   Function Templates Core

A template is a blueprint the compiler uses to generate code for specific types. Write the algorithm once — the compiler stamps out correct versions for every type you use it with. This happens entirely at compile time — zero runtime overhead versus hand-written per-type functions.

Template Instantiation & Header-Only Rule

When you write maxVal(3, 5), the compiler looks at the template, deduces T = int, and generates a concrete int maxVal(int, int) function just for integers. This is instantiation. If you later call it with double, it generates a totally separate function for double. Because the compiler must see the full template source code to generate these new functions on demand, templates must be entirely written in header files (.h/.hpp)—you cannot hide their implementation in a .cpp file! Furthermore, if you instantiate a class like std::vector with 50 different types, the compiler literally generates 50 full copies of the vector code. This is called Template Code Bloat and explains why heavy template usage balloons executable sizes and compile times.

#include <iostream> using namespace std; // Function template — T is a type parameter template<typename T> T maxVal(const T& a, const T& b) { return (a > b) ? a : b; } // Multiple type parameters template<typename T, typename U> auto add(T a, U b) -> decltype(a + b) { // trailing return type return a + b; } // Non-type template parameter (compile-time constant) template<typename T, int N> void printArray(T (&arr)[N]) { // N deduced at compile time for (int i = 0; i < N; i++) cout << arr[i] << " "; cout << "\n"; } int main() { cout << maxVal(3, 5) << "\n"; // T=int, result: 5 cout << maxVal(3.14, 2.71) << "\n"; // T=double, result: 3.14 cout << maxVal<string>("apple","banana") << "\n"; // explicit T cout << add(3, 4.5) << "\n"; // T=int, U=double → 7.5 int arr[] = {5, 3, 1, 4, 2}; printArray(arr); // N=5 deduced automatically }

Template Specialization

// Primary template template<typename T> void printType(T val) { cout << "Value: " << val << "\n"; } // Full specialization for const char* — different behavior template<> void printType<const char*>(const char* val) { cout << "C-string: \"" << val << "\"\n"; } // Full specialization for bool template<> void printType<bool>(bool val) { cout << "Boolean: " << (val ? "true" : "false") << "\n"; } printType(42); // Value: 42 printType("hello"); // C-string: "hello" printType(true); // Boolean: true

1.2   Class Templates Core

Class templates let you build generic data structures — the foundation of the entire STL. vector<T>, pair<K,V>, map<K,V> — all class templates.

// Generic Stack — works for ANY type T template<typename T, int MaxSize = 100> // default template param class Stack { private: T data[MaxSize]; int top_; public: Stack() : top_(-1) {} void push(const T& val) { if (top_ >= MaxSize - 1) throw std::overflow_error("Stack full"); data[++top_] = val; } T pop() { if (empty()) throw std::underflow_error("Stack empty"); return data[top_--]; } T& peek() { if (empty()) throw std::underflow_error("Stack empty"); return data[top_]; } bool empty() const { return top_ == -1; } int size() const { return top_ + 1; } }; // Instantiate for different types — completely independent classes Stack<int> intStack; Stack<string> strStack; Stack<double, 50> smallDoubleStack; // MaxSize=50 intStack.push(10); intStack.push(20); intStack.push(30); cout << intStack.pop() << "\n"; // 30 — LIFO strStack.push("hello"); strStack.push("world"); cout << strStack.peek() << "\n"; // "world" — peek doesn't remove

1.3   Variadic Templates & Fold Expressions C++11/17

Variadic templates accept any number of type parameters. Combined with C++17 fold expressions, they unlock elegant recursive and pack-processing code.

// C++11 variadic template — recursive unpacking template<typename T> T sum(T val) { return val; } // Base case: one argument template<typename T, typename... Args> // ... = parameter pack T sum(T first, Args... rest) { return first + sum(rest...); // Peel first, recurse on rest } cout << sum(1, 2, 3, 4, 5) << "\n"; // 15 cout << sum(1.1, 2.2, 3.3) << "\n"; // 6.6 // C++17 Fold Expressions — much cleaner! template<typename... Args> auto foldSum(Args... args) { return (... + args); // Unary left fold: ((args[0] + args[1]) + args[2]) + ... } template<typename... Args> void printAll(Args... args) { (cout << ... << args) << "\n"; // Fold over << } cout << foldSum(1, 2, 3, 4, 5) << "\n"; // 15 printAll("x=", 5, " y=", 3.14, " ok"); // x=5 y=3.14 ok // sizeof... — number of arguments in pack template<typename... T> void countArgs(T...) { cout << sizeof...(T) << " args\n"; } countArgs(1, "hi", 3.14); // 3 args

1.4   Concepts (C++20) — Constraining Templates C++20

Before C++20, template errors produced famously unreadable error messages. Concepts let you express constraints on template parameters explicitly — better errors, self-documenting code.

#include <concepts> // Built-in concepts from <concepts> template<std::integral T> // T must be an integer type T gcd(T a, T b) { while (b) { a %= b; swap(a, b); } return a; } template<std::floating_point T> // T must be float/double/long double T square(T x) { return x * x; } // Define your own concept template<typename T> concept Printable = requires(T t) { // T must support operator<< { std::cout << t }; }; template<Printable T> void printValue(T val) { cout << val << "\n"; } // Addable concept template<typename T> concept Addable = requires(T a, T b) { { a + b } -> std::same_as<T>; // a+b must return T }; gcd(48, 18); // OK: int is integral gcd(3.5, 1.5); // COMPILE ERROR: double not integral — clear message!
Before C++20 — Use static_assert

Without concepts, use static_assert with type traits: static_assert(std::is_integral_v<T>, "T must be integral");. It's less elegant but works back to C++11 and gives a reasonable error message.

1.5   Template Traps & Key Points

TrapProblemFix
Template in .cpp fileLinker error — templates must be fully defined where usedPut template definitions in header files (.h/.hpp), not .cpp
Implicit narrowing in deductionmaxVal(3, 3.5) — T deduced as int AND double → ambiguity errorBe explicit: maxVal<double>(3, 3.5) or cast: maxVal(3.0, 3.5)
Code bloatTemplates generate separate code for every instantiated typeUse extern template to suppress redundant instantiations in multi-TU projects
Unreadable errors (pre-C++20)40-line error message for a simple type mismatchAdd static_assert with clear messages; upgrade to C++20 concepts
↑ back to top
Chapter 02
STL Containers — Every Data Structure You Need

2.1   Container Overview — Which One When? Decision Guide

ContainerTypeAccessInsert/DeleteUse When
vector<T>SequenceO(1) randomO(1) end / O(n) midDefault choice. Contiguous memory, cache-friendly.
deque<T>SequenceO(1) randomO(1) front & backNeed fast push/pop at both ends.
list<T>SequenceO(n) linearO(1) anywhereFrequent mid-sequence insert/delete with existing iterator.
array<T,N>SequenceO(1) randomFixed sizeFixed-size stack array with STL interface.
map<K,V>AssociativeO(log n)O(log n)Sorted key-value pairs. Ordered iteration.
unordered_map<K,V>HashO(1) avgO(1) avgFast lookup by key. Order doesn't matter.
set<T>AssociativeO(log n)O(log n)Unique sorted elements. Membership testing.
unordered_set<T>HashO(1) avgO(1) avgFast unique membership. Order doesn't matter.
stack<T>Adaptortop onlyO(1) topLIFO. Wraps deque by default.
queue<T>Adaptorfront/backO(1)FIFO. BFS, task queues.
priority_queue<T>Adaptortop onlyO(log n)Max-heap by default. Dijkstra, scheduling.

2.2   vector — The Workhorse Container Most Used

#include <vector> using namespace std; // Construction vector<int> v1; // empty vector<int> v2(5, 0); // {0,0,0,0,0} vector<int> v3 = {1, 2, 3, 4, 5}; // initializer list vector<int> v4(v3.begin(), v3.end()); // copy from range vector<vector<int>> matrix(3, vector<int>(4, 0)); // 3x4 matrix // Adding / removing elements v1.push_back(10); // Add to end: [10] v1.push_back(20); // [10, 20] v1.emplace_back(30); // Construct in-place (faster for objects) v1.pop_back(); // Remove last: [10, 20] v1.insert(v1.begin(), 5); // Insert at front: [5, 10, 20] v1.erase(v1.begin()); // Erase first: [10, 20] v1.clear(); // Remove all elements // Access v3[0] // 1 — no bounds check (fast) v3.at(0) // 1 — bounds-checked (throws std::out_of_range) v3.front() // 1 — first element v3.back() // 5 — last element v3.data() // raw pointer to internal array (for C APIs) // Size management v3.size() // 5 — number of elements v3.capacity() // >= 5 — allocated storage (may be larger) v3.empty() // false v3.resize(8, 0) // [1,2,3,4,5,0,0,0] — extend with zeros v3.reserve(100) // Pre-allocate 100 slots — avoids repeated reallocation v3.shrink_to_fit() // Release excess capacity // Iteration — all equivalent ways for (int i = 0; i < v3.size(); i++) cout << v3[i] << " "; for (auto& x : v3) cout << x << " "; // range-for (preferred) for (auto it = v3.begin(); it != v3.end(); ++it) cout << *it << " ";
push_back vs emplace_back

push_back(obj) copies or moves an existing object into the vector. emplace_back(args...) constructs the object directly inside the vector's storage — zero copy, zero move. For complex objects, always prefer emplace_back. For simple types like int, the difference is negligible.

Iterator Invalidation After push_back

When push_back causes a reallocation (capacity exceeded), all iterators, pointers, and references to the vector are invalidated. Never hold a pointer into a vector while modifying it. If you need stable references, use reserve(n) upfront, or use a deque or list.

2.3   map & unordered_map — Key-Value Lookup Essential

#include <map> #include <unordered_map> // map — sorted by key (uses Red-Black Tree), O(log n) ops map<string, int> wordCount; wordCount["apple"] = 3; wordCount["banana"] = 1; wordCount["cherry"] = 2; wordCount["apple"]++; // Increment existing: apple = 4 // Safe lookup — at() throws if key missing, [] INSERTS default! wordCount.at("apple"); // 4 — throws if "apple" not found wordCount["date"]; // INSERTS "date"=0 if not found (side effect!) // Check existence before access if (wordCount.count("apple")) cout << "Found\n"; if (wordCount.find("apple") != wordCount.end()) cout << "Found\n"; // C++20: contains() if (wordCount.contains("apple")) cout << "Found\n"; // Iteration — sorted by key for (auto& [key, val] : wordCount) { // C++17 structured binding cout << key << ": " << val << "\n"; } // apple: 4, banana: 1, cherry: 2 (alphabetical order) // Erase wordCount.erase("banana"); wordCount.erase(wordCount.find("cherry")); // By iterator // unordered_map — hash table, O(1) avg, NO guaranteed order unordered_map<string, int> freq; freq.reserve(100); // Reserve buckets to avoid rehashing freq["cat"] = 1; freq["dog"] = 2; // Same API as map — count, find, contains, [], at, erase, iteration // But: ~3-5x faster for lookup-heavy code
map[] Creates Elements — The Silent Bug

myMap["key"] is NOT a read-only lookup. If "key" doesn't exist, it inserts a default-constructed value for that key. This is usually a bug in read-only contexts. Use myMap.find("key") or myMap.at("key") when you want to check/read without inserting. This is one of the most common map bugs.

2.4   set, stack, queue & priority_queue Core

#include <set> #include <stack> #include <queue> // ── SET ───────────────────────────────────────────── set<int> s = {5, 3, 1, 4, 2, 3, 1}; // Duplicates removed, sorted // s = {1, 2, 3, 4, 5} s.insert(6); // {1,2,3,4,5,6} s.erase(3); // {1,2,4,5,6} s.count(4); // 1 (exists) or 0 (not exists) s.lower_bound(4); // Iterator to first element >= 4 s.upper_bound(4); // Iterator to first element > 4 // multiset — allows duplicates, still sorted multiset<int> ms = {3, 1, 4, 1, 5, 9, 2, 6, 5}; ms.count(1); // 2 — how many times 1 appears ms.erase(ms.find(1)); // Erase ONE occurrence (not all) // ── STACK ──────────────────────────────────────────── stack<int> stk; stk.push(1); stk.push(2); stk.push(3); stk.top(); // 3 — view top without removing stk.pop(); // Removes 3 stk.empty(); // false stk.size(); // 2 // ── QUEUE ──────────────────────────────────────────── queue<int> q; q.push(1); q.push(2); q.push(3); q.front(); // 1 — first element (oldest) q.back(); // 3 — last element (newest) q.pop(); // Removes 1 (FIFO) // ── PRIORITY QUEUE (max-heap by default) ───────────── priority_queue<int> maxPQ; maxPQ.push(3); maxPQ.push(1); maxPQ.push(4); maxPQ.push(1); maxPQ.push(5); maxPQ.top(); // 5 — always returns maximum maxPQ.pop(); // Removes 5 // Min-heap: use greater<int> comparator priority_queue<int, vector<int>, greater<int>> minPQ; minPQ.push(3); minPQ.push(1); minPQ.push(4); minPQ.top(); // 1 — returns minimum // Custom comparator for pairs using pii = pair<int,int>; priority_queue<pii, vector<pii>, greater<pii>> pqPairs; pqPairs.push({3, 10}); pqPairs.push({1, 20}); pqPairs.top(); // {1,20} — sorted by first element

2.5   Container Traps & Key Points

TrapWrongCorrect
map[] inserts on readif (m["key"] == 0) — inserts "key" with 0!if (m.count("key") && m["key"] == 0) or m.find()
Iterating while erasingfor(auto it=v.begin(); it!=v.end(); it++) if(bad) v.erase(it);it = v.erase(it); — erase returns next valid iterator
vector reallocationStoring pointer to v[0] then push_back — dangling pointerreserve(n) upfront or use indices, not pointers
set::erase all duplicatess.erase(val) on multiset removes ALL copiess.erase(s.find(val)) removes exactly one copy
Comparing size() to -1if (v.size() - 1 >= 0) — size() is unsigned! Always trueCast: if ((int)v.size() - 1 >= 0) or check v.empty() first
↑ back to top
Chapter 03
STL Algorithms & Iterators — Do More With Less Code

3.1   Iterators — The Glue Between Containers and Algorithms Core

An iterator is an object that points into a container and can advance through it. The STL's power comes from algorithm + iterator + container independence — any algorithm works with any container that provides compatible iterators.

Iterator Hierarchy Input ──► Forward ──► Bidirectional ──► RandomAccess ──► Contiguous (read,1pass) (read/write, (++ and --) ([], +n, -n, (ptr arithmetic, multi-pass) <, difference) guaranteed contiguous) Container iterator types: vector, array, deque ──► RandomAccess (fastest, supports all ops) list, map, set ──► Bidirectional (++ and -- only) forward_list ──► Forward (++ only) istream ──► Input (single-pass read)
#include <iterator> vector<int> v = {1, 2, 3, 4, 5}; // Iterator basics auto it = v.begin(); // Points to first element (1) auto end = v.end(); // Points ONE PAST last element (sentinel) *it; // Dereference: 1 ++it; // Advance: now points to 2 it += 2; // RandomAccess: now points to 4 it - v.begin(); // Distance from start: 3 // Reverse iterators for (auto rit = v.rbegin(); rit != v.rend(); ++rit) cout << *rit << " "; // 5 4 3 2 1 // const iterators — read-only traversal for (auto cit = v.cbegin(); cit != v.cend(); ++cit) cout << *cit << " "; // Iterator adapters #include <iterator> back_inserter(v) // Output iterator that push_backs front_inserter(myList) // Output iterator that push_fronts (list/deque only) inserter(s, s.begin()) // Output iterator that inserts at position // istream_iterator — read from stdin into container vector<int> nums( istream_iterator<int>(cin), istream_iterator<int>() // end-of-stream );

3.2   Essential STL Algorithms Must Know

#include <algorithm> #include <numeric> vector<int> v = {5, 3, 8, 1, 9, 2, 7, 4, 6}; // ── SORTING ───────────────────────────────────────────── sort(v.begin(), v.end()); // Ascending: {1,2,3,4,5,6,7,8,9} sort(v.begin(), v.end(), greater<int>()); // Descending: {9,8,7,6,5,4,3,2,1} sort(v.begin(), v.end(), [](int a, int b) { // Custom comparator return abs(a) < abs(b); // Sort by absolute value }); stable_sort(v.begin(), v.end()); // Preserves relative order of equals partial_sort(v.begin(), v.begin()+3, v.end()); // Only sort first 3 elements // ── SEARCHING ─────────────────────────────────────────── auto it = find(v.begin(), v.end(), 5); // Linear search: O(n) if (it != v.end()) cout << "Found at " << (it - v.begin()); // Binary search (REQUIRES SORTED range) sort(v.begin(), v.end()); binary_search(v.begin(), v.end(), 5); // bool: true if found lower_bound(v.begin(), v.end(), 5); // Iterator to first element >= 5 upper_bound(v.begin(), v.end(), 5); // Iterator to first element > 5 equal_range(v.begin(), v.end(), 5); // pair: [lower, upper) range of 5s // ── COUNTING & CONDITIONALS ────────────────────────────── count(v.begin(), v.end(), 5); // Count occurrences of 5 count_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; }); // Count evens any_of(v.begin(), v.end(), [](int x){ return x > 8; }); // Any > 8? all_of(v.begin(), v.end(), [](int x){ return x > 0; }); // All positive? none_of(v.begin(), v.end(), [](int x){ return x < 0; }); // None negative? // ── TRANSFORMING ──────────────────────────────────────── transform(v.begin(), v.end(), v.begin(), // In-place square [](int x){ return x * x; }); vector<int> result; transform(v.begin(), v.end(), back_inserter(result), [](int x){ return x * 2; }); // Double each into new vector for_each(v.begin(), v.end(), [](int& x){ x += 1; }); // Add 1 to each element // ── REDUCE / ACCUMULATE ────────────────────────────────── int total = accumulate(v.begin(), v.end(), 0); // Sum. 0 = initial int product = accumulate(v.begin(), v.end(), 1, [](int acc, int x){ return acc * x; }); // Product // C++17 parallel reduce (if hardware supports) #include <execution> int par_sum = reduce(execution::par, v.begin(), v.end(), 0); // ── MODIFYING ─────────────────────────────────────────── fill(v.begin(), v.end(), 0); // Set all to 0 iota(v.begin(), v.end(), 1); // Fill: 1, 2, 3, 4, ... (from <numeric>) reverse(v.begin(), v.end()); // Reverse in place rotate(v.begin(), v.begin()+2, v.end()); // Left-rotate by 2 unique(v.begin(), v.end()); // Remove CONSECUTIVE duplicates // IMPORTANT: sort first, THEN unique, then erase for full dedup: sort(v.begin(), v.end()); auto last = unique(v.begin(), v.end()); v.erase(last, v.end()); // ── MIN/MAX ───────────────────────────────────────────── *min_element(v.begin(), v.end()); // Value of minimum element *max_element(v.begin(), v.end()); // Value of maximum element minmax_element(v.begin(), v.end()); // pair of iterators {min, max} min({3, 1, 4, 1, 5}); // C++11: initializer list max({3, 1, 4, 1, 5});

3.3   Ranges (C++20) — Cleaner Pipeline Syntax C++20

#include <ranges> #include <algorithm> using namespace std::ranges; using namespace std::views; vector<int> v = {5, 3, 8, 1, 9, 2, 7, 4, 6}; // ranges algorithms — no more begin()/end() boilerplate sort(v); // ranges::sort takes the whole container auto it = find(v, 5); // ranges::find // views — lazy pipelines, no data copied auto evenSquares = v | filter([](int x){ return x % 2 == 0; }) // Keep evens | transform([](int x){ return x * x; }); // Square them for (int x : evenSquares) cout << x << " "; // Computed lazily on demand // take, drop, reverse, iota views for (int x : v | take(3)) cout << x << " "; // First 3 elements for (int x : v | drop(3)) cout << x << " "; // Skip first 3 for (int x : v | reverse) cout << x << " "; // Iterate in reverse for (int x : iota_view(1, 11)) cout << x << " "; // 1 2 3 ... 10

3.4   Algorithm Traps & Key Points

TrapWrongCorrect
unique doesn't shrink vectorunique(v.begin(), v.end()); — "removed" elements still in memoryauto e = unique(…); v.erase(e, v.end()); to actually remove
binary_search on unsorted rangebinary_search on unsorted — undefined behaviorsort first, then binary_search/lower_bound/upper_bound
lower_bound return valueint idx = lower_bound(…, val); — returns iterator, not intint idx = lower_bound(v.begin(), v.end(), val) - v.begin();
Modifying range in for_eachfor_each(…, [](int x){x++;}) — x is a copy!for_each(…, [](int& x){x++;}) — use reference
↑ back to top
Chapter 04
Smart Pointers — Automatic Memory Management

4.1   RAII — The Most Important Rule in C++ Must Know

Resource Acquisition Is Initialization (RAII)

RAII is the defining idiom that makes modern C++ safe. In languages like Java or C#, you rely on a Garbage Collector to clean up memory eventually, but you still have to manually close file handles and release locks (usually in scattered finally blocks). In C++, you tie all resources (heap memory, network sockets, DB connections, mutexes) to the lifespan of an object on the stack. When the object is created (Acquisition), it opens the file/memory. When the object goes out of scope—for any reason, including early return or thrown exceptions—its destructor runs instantly and deterministically cleans up the resource. There is no Garbage Collector needed, no finally blocks to remember, and no leaks if you follow this rule. Smart pointers are simply RAII applied to heap memory.

4.2   unique_ptr — Exclusive Ownership Default Choice

A unique_ptr owns an object exclusively — one pointer, one owner. When the unique_ptr goes out of scope, the object is automatically deleted. Zero overhead compared to raw pointers. Use this for 90% of heap allocations.

#include <memory> // Create — ALWAYS use make_unique, never raw new auto p1 = std::make_unique<int>(42); // unique_ptr<int> auto p2 = std::make_unique<string>("hello"); // unique_ptr<string> auto p3 = std::make_unique<BankAccount>("Alice", 1000.0); // Custom type // Use like a raw pointer cout << *p1 << "\n"; // Dereference: 42 cout << p3->getBalance(); // Member access p1.get(); // Get raw pointer (use sparingly) // Ownership — can MOVE, cannot COPY auto p4 = std::move(p1); // p4 now owns the int. p1 is nullptr. // auto p5 = p4; ERROR: unique_ptr is not copyable // Pass to function — by reference (read only), by move (transfer) void observe(const unique_ptr<int>& p); // Borrow — caller keeps ownership void consume(unique_ptr<int> p); // Take ownership (moved in) void use(int* raw) { ... } // Legacy API — use p.get() // Release and reset int* raw = p4.release(); // Give up ownership — p4 is nullptr, YOU manage raw p4.reset(); // Delete the object and set to nullptr p4.reset(new int(99)); // Delete old, own new (avoid — prefer make_unique) // Arrays auto arr = std::make_unique<int[]>(10); // unique_ptr to array of 10 ints arr[0] = 42; // Use operator[] // Automatically calls delete[] when out of scope

4.3   shared_ptr — Shared Ownership When You Need Sharing

A shared_ptr uses reference counting — multiple pointers can own the same object. The object is deleted when the last shared_ptr to it is destroyed. Has overhead: an atomic reference count per object.

auto sp1 = std::make_shared<int>(42); auto sp2 = sp1; // COPY OK — ref count becomes 2 auto sp3 = sp1; // ref count = 3 cout << sp1.use_count() << "\n"; // 3 cout << *sp1 << "\n"; // 42 { auto sp4 = sp1; // ref count = 4 } // sp4 destroyed → ref count = 3 sp2.reset(); // sp2 no longer points → ref count = 2 sp3.reset(); // ref count = 1 // sp1 is the last owner // When sp1 goes out of scope: ref count → 0 → object deleted // Shared ownership in containers vector<shared_ptr<Animal>> zoo; zoo.push_back(make_shared<Dog>("Rex", 3, "Lab")); zoo.push_back(make_shared<Cat>("Whiskers", 2)); for (auto& a : zoo) a->speak(); // Polymorphism works through shared_ptr
Cyclic References — The shared_ptr Memory Leak

If object A holds a shared_ptr to B, and B holds a shared_ptr back to A, neither will ever be destroyed — ref count never reaches zero. Break cycles with weak_ptr: use shared_ptr for the "owning" direction and weak_ptr for the "back" reference.

4.4   weak_ptr — Non-Owning Observer Cycle Breaker

// weak_ptr observes a shared_ptr without owning it // Does NOT prevent the object from being deleted // MUST lock() to use — lock() returns a shared_ptr (or nullptr if expired) auto sp = make_shared<int>(42); weak_ptr<int> wp = sp; // wp observes sp — ref count stays 1 // Using weak_ptr safely if (auto locked = wp.lock()) { // lock() returns shared_ptr (valid) or nullptr cout << *locked << "\n"; // 42 — object still alive } sp.reset(); // Object destroyed wp.expired(); // true — object gone if (auto locked = wp.lock()) { // lock() now returns nullptr // never executed } else { cout << "Object is gone\n"; } // Breaking cyclic reference class Node { public: shared_ptr<Node> next; weak_ptr<Node> parent; // weak — avoids cycle with child→parent→child int data; };
Smart Pointer Decision Tree

Unique ownership → unique_ptr (default for 90% of cases).
Shared ownership (truly multiple owners) → shared_ptr.
Observe without owning (cache, back-pointer, cycle break) → weak_ptr.
Never use raw new/delete in new code. When interfacing with C APIs, get the raw pointer with .get() — never release ownership.

4.5   Smart Pointer Traps & Key Points

TrapWrongCorrect
Two shared_ptrs from raw pointershared_ptr<T> a(raw); shared_ptr<T> b(raw); — double free!Use make_shared once; copy the shared_ptr to share ownership
Not using make_unique/make_sharedshared_ptr<T> p(new T()); — two allocations, exception-unsafemake_shared<T>() — one allocation, exception-safe
Storing this in shared_ptrshared_ptr<Foo> sp(this); — independent count, double freeInherit enable_shared_from_this, use shared_from_this()
weak_ptr without lock*wp — weak_ptr has no operator* (can't dereference directly)auto sp = wp.lock(); if (sp) { use *sp; }
Passing unique_ptr by value to constructorFoo(unique_ptr<T> p) — caller must std::move the argFoo(unique_ptr<T>&& p) or document that caller must move
↑ back to top
Chapter 05
Lambdas & Functional C++ — Anonymous Functions & Higher-Order Code

5.1   Lambda Anatomy — Every Part Explained Core

Lambda Syntax [ capture ] ( params ) specifiers -> return_type { body } │ │ │ │ │ │ │ │ │ └── Function body │ │ │ └── Optional: explicit return type │ │ └── Optional: mutable, noexcept, constexpr │ └── Optional: parameters (like a regular function) └── Required: capture list — what from outer scope the lambda can see
// The simplest lambda auto greet = []() { cout << "Hello!\n"; }; greet(); // Call it: Hello! // With parameters and return type auto add = [](int a, int b) -> int { return a + b; }; cout << add(3, 4); // 7 // Return type deduced (usually fine) auto square = [](double x) { return x * x; }; // ── CAPTURE LIST ───────────────────────────────────── int x = 10, y = 20; // [] — capture nothing (can't access outer variables) // [=] — capture ALL outer vars BY COPY (read-only) // [&] — capture ALL outer vars BY REFERENCE (can modify) // [x] — capture x by copy only // [&x] — capture x by reference only // [=, &x] — capture all by copy, but x by reference // [this] — capture this pointer (in member functions) auto addX = [x](int n) { return n + x; }; // Copy of x auto addXref = [&x](int n) { return n + x; }; // Reference to x auto addBoth = [x, y](int n) { return n+x+y; }; x = 99; cout << addX(0) << "\n"; // 10 — captured x=10 at lambda creation cout << addXref(0) << "\n"; // 99 — reference sees current x // mutable — allow modifying captured-by-copy variables auto counter = [count = 0]() mutable { // init-capture (C++14) return ++count; }; cout << counter() << "\n"; // 1 cout << counter() << "\n"; // 2 — count is stateful! cout << counter() << "\n"; // 3 // Generic lambda (C++14) — auto parameters auto printAny = [](auto val) { cout << val << "\n"; }; printAny(42); // int printAny("hello"); // const char* printAny(3.14); // double

5.2   std::function, bind & Higher-Order Functions Core

#include <functional> // std::function — type-erased callable wrapper // Can hold: lambdas, regular functions, functors, bind results std::function<int(int,int)> op; op = [](int a, int b) { return a + b; }; cout << op(3, 4) << "\n"; // 7 op = [](int a, int b) { return a * b; }; cout << op(3, 4) << "\n"; // 12 // Store in container (callback list) vector<std::function<void()>> callbacks; callbacks.push_back([]{ cout << "Event 1\n"; }); callbacks.push_back([]{ cout << "Event 2\n"; }); for (auto& cb : callbacks) cb(); // std::bind — bind arguments (pre-C++11 style, lambdas are preferred) auto addFive = std::bind(add, std::placeholders::_1, 5); cout << addFive(10) << "\n"; // 15 // Higher-order functions using lambdas auto compose = [](auto f, auto g) { return [f, g](auto x) { return f(g(x)); }; // f(g(x)) }; auto doubleSquare = compose( [](int x){ return x * x; }, // outer: square [](int x){ return x * 2; } // inner: double ); cout << doubleSquare(3) << "\n"; // (3*2)^2 = 36 // Memoization wrapper auto memoize = [](auto f) { map<int,int> cache; return [f, cache](int n) mutable -> int { if (!cache.count(n)) cache[n] = f(n); return cache[n]; }; };

5.3   Lambda Traps & Key Points

TrapProblemFix
Dangling reference capture[&x] after x is destroyed — dangling referenceUse [x] (copy) when lambda outlives the variable
[=] captures this implicitly[=] in a member function captures this by pointer — if object dies, UBExplicitly [this] or [*this] (C++17 — copy of object)
std::function overheadUsing std::function for every lambda — heap alloc, virtual dispatchUse auto or template parameter for callbacks in performance-critical code
Recursive lambdaauto f = [](int n){ return n==0?1:n*f(n-1); } — f not captured yet!Use std::function<int(int)> f = [&f](int n){...}; — captures f by ref
↑ back to top
Chapter 06
Exception Handling — Error Propagation Done Right

6.1   try / catch / throw — The Full System Core

Motivation: Why Return Codes Suck

Before exceptions, functions returned error codes (e.g., -1) to indicate failure. This creates huge problems: 1) You must litter every single function call with if (status == -1) return -1;, drowning the "happy path" in error-checking noise. 2) If you forget to check the return code, the program silently proceeds in a corrupted state. 3) If a function perfectly computes an int and the answer happens to be -1, you have overlapping data types. Exceptions solve this by completely separating error-handling from logic, and unlike return codes, an exception cannot be silently ignored. It demands attention or terminates the program safely.

Stack Unwinding Step-by-Step

When you throw an exception, C++ begins a process called Stack Unwinding. It works backwards through the call stack, doing two things at each level until it finds a matching catch block: it checks for a try block, and if it doesn't find one, it destroys all local variables in that scope (calling their destructors). This is why RAII + Exceptions makes C++ uniquely safe—the unwinding process perfectly vacuums up all memory, closes all files, and releases all locks as it flies back up the stack!

#include <stdexcept> #include <exception> // Throw: signal that something went wrong double safeDivide(double a, double b) { if (b == 0.0) throw std::runtime_error("Division by zero"); // Throw an exception object return a / b; } // Catch: handle exceptions try { double result = safeDivide(10.0, 0.0); cout << "Result: " << result << "\n"; // Never reached if exception thrown } catch (const std::runtime_error& e) { // Catch by const reference (always) cerr << "Runtime error: " << e.what() << "\n"; } catch (const std::exception& e) { // Base class — catches most std exceptions cerr << "Standard exception: " << e.what() << "\n"; } catch (...) { // Catch-all — last resort cerr << "Unknown exception\n"; throw; // Re-throw! Don't silently swallow. }
Exception ClassHeaderUse For
std::exception<exception>Base class for all standard exceptions
std::runtime_error<stdexcept>Errors detected only at runtime
std::logic_error<stdexcept>Programming errors, invariant violations
std::invalid_argument<stdexcept>Bad function argument
std::out_of_range<stdexcept>Index / value out of valid range
std::overflow_error<stdexcept>Arithmetic overflow
std::bad_alloc<new>Memory allocation failure (new throws this)
std::bad_cast<typeinfo>Failed dynamic_cast to reference

6.2   Custom Exceptions & Exception Hierarchies Core

// Custom exception — always inherit from std::exception or its subclasses class DatabaseError : public std::runtime_error { int errorCode_; public: DatabaseError(const string& msg, int code) : std::runtime_error(msg), errorCode_(code) {} int errorCode() const { return errorCode_; } // what() is inherited from runtime_error }; class ConnectionError : public DatabaseError { public: ConnectionError(std::string host) : DatabaseError("Failed to connect to " + host, 1001) {} }; class QueryError : public DatabaseError { public: QueryError(std::string sql) : DatabaseError("Invalid query: " + sql, 1002) {} }; // Catching hierarchy — catch specific before general try { throw ConnectionError("db.example.com"); } catch (const ConnectionError& e) { // Most specific first cerr << "Connection failed: " << e.what() << " (code " << e.errorCode() << ")\n"; } catch (const DatabaseError& e) { // More general cerr << "DB error: " << e.what() << "\n"; } catch (const std::exception& e) { // Most general std exception cerr << "Error: " << e.what() << "\n"; }

6.3   noexcept, Exception Safety & RAII Critical

// noexcept — promise that this function won't throw void safeSwap(int& a, int& b) noexcept { int temp = a; a = b; b = temp; } // If noexcept function throws, std::terminate() is called immediately // STL uses noexcept to optimize — e.g., vector uses move-ctor only if noexcept // noexcept(expression) — conditional noexcept template<typename T> void mySwap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) { T temp = std::move(a); a = std::move(b); b = std::move(temp); }
Four Levels of Exception Safety

No guarantee — the program may be in an inconsistent state if an exception occurs.
Basic guarantee — no resources are leaked; the program is in some valid (but unspecified) state.
Strong guarantee — the operation either succeeds completely, or has no effect (commit-or-rollback).
No-throw guarantee — the operation never throws (noexcept). Highest level. Always achieve at minimum the basic guarantee.

RAII Makes Exception Safety Easy

With RAII, you get the basic guarantee automatically — when an exception unwinds the stack, all destructors run, all resources are freed. To get the strong guarantee, use the "copy-and-swap" idiom: work on a copy, then swap atomically if everything succeeded. The swap itself should be noexcept.

6.4   Exception Traps & Key Points

TrapProblemFix
Catch by valuecatch(std::exception e) — copies and slices derived typecatch(const std::exception& e) — always catch by const reference
Catching most-general firstcatch(exception) before catch(runtime_error) — specific never reachedCatch most-specific first, most-general last
Swallowing exceptions silentlycatch(...) {} — hides bugs, impossible to debugAt minimum log; re-throw with throw; if you can't handle it
Throwing in destructor~Foo() throws — if already unwinding, std::terminate calledNever throw from destructors; catch internally and log/ignore
Using exceptions for control flowthrow/catch to break out of loops — extremely slowExceptions are for exceptional (error) conditions, not normal flow
↑ back to top
Chapter 07
File I/O & Streams — Reading, Writing & Formatting

7.1   File Reading & Writing — fstream, ifstream, ofstream Core

#include <fstream> #include <sstream> using namespace std; // ── WRITING ───────────────────────────────────────────── ofstream outFile("output.txt"); // Opens (creates/overwrites) if (!outFile.is_open()) { cerr << "Failed to open file\n"; return 1; } outFile << "Hello, File!\n"; outFile << "Value: " << 42 << "\n"; outFile.close(); // Optional: RAII closes automatically // Append mode ofstream appFile("log.txt", ios::app); // Don't overwrite — append appFile << "New log entry\n"; // ── READING ───────────────────────────────────────────── ifstream inFile("data.txt"); if (!inFile) { cerr << "Cannot open data.txt\n"; return 1; } // Read word by word string word; while (inFile >> word) { cout << word << "\n"; } // Read line by line inFile.clear(); inFile.seekg(0); // Reset to beginning string line; while (getline(inFile, line)) { cout << line << "\n"; } // Read entire file into string inFile.clear(); inFile.seekg(0); string content((istreambuf_iterator<char>(inFile)), istreambuf_iterator<char>()); // ── STRINGSTREAM — in-memory I/O ───────────────────────── ostringstream oss; oss << "Name: " << "Alice" << " Age: " << 25; string result = oss.str(); // "Name: Alice Age: 25" istringstream iss("42 3.14 hello"); int n; double d; string s; iss >> n >> d >> s; // n=42, d=3.14, s="hello" // Parse CSV line string csvLine = "Alice,25,Engineer"; istringstream csvStream(csvLine); string token; while (getline(csvStream, token, ',')) { // ',' as delimiter cout << token << "\n"; }

7.2   Stream Formatting & Manipulators Core

#include <iomanip> // Width, fill, alignment cout << setw(10) << "hello" << "\n"; // " hello" (right-aligned, width 10) cout << left << setw(10) << "hello" << "\n"; // "hello " (left-aligned) cout << setfill('*') << setw(10) << 42 << "\n"; // "********42" // Floating point cout << fixed << setprecision(2) << 3.14159 << "\n"; // 3.14 cout << scientific << setprecision(3) << 12345.6789; // 1.235e+04 cout << defaultfloat; // Reset to default // Number bases cout << hex << 255 << "\n"; // ff cout << oct << 255 << "\n"; // 377 cout << dec << 255 << "\n"; // 255 (reset to decimal) cout << showbase << hex << 255; // 0xff (with prefix) // Boolean cout << boolalpha << true << "\n"; // "true" (not 1) cout << noboolalpha << true << "\n"; // "1" // C++20: std::format — Python f-string style #include <format> string s = std::format("Name: {:10} Age: {:3d} Score: {:.2f}", "Alice", 25, 98.5); cout << s << "\n"; // Name: Alice Age: 25 Score: 98.50

7.3   File I/O Traps & Key Points

TrapProblemFix
Not checking if file openedRead from closed ifstream — silently reads nothingif (!inFile) { /* handle error */ } after opening
Mixing >> and getlinecin >> n; getline(cin, line); — reads empty linecin.ignore(numeric_limits<streamsize>::max(), '\n') before getline
Not resetting stream stateAfter EOF or error, stream is in fail state — reads silently failinFile.clear(); before re-reading; check .good()/.fail()
setw is not stickycout << setw(10) — only affects the NEXT single outputfixed, scientific, setfill are sticky. setw must be repeated each time.
↑ back to top
Chapter 08
Modern C++11–20 — Every Feature That Changed the Language

8.1   C++11 — The Revolution Baseline

// ── AUTO & DECLTYPE ───────────────────────────────────── auto x = 42; // int auto v = vector<int>{1,2,3}; // vector<int> auto f = [](int x){ return x*x; }; // lambda type decltype(x) y = 5; // y has same type as x (int) // ── RANGE-BASED FOR ────────────────────────────────────── for (auto& elem : v) elem *= 2; // Modify in-place for (const auto& elem : v) cout << elem; // Read-only // ── INITIALIZER LISTS ──────────────────────────────────── vector<int> nums = {1, 2, 3, 4, 5}; map<string,int> ages = {{"Alice",25}, {"Bob",30}}; // ── NULLPTR ────────────────────────────────────────────── int* p = nullptr; // Type-safe null. NOT 0 or NULL (which are ints) // ── UNIFORM INITIALIZATION ─────────────────────────────── int a{5}; // Brace init — narrowing check MyClass obj{1, 2.0, "three"}; // Works for any type // ── static_assert ──────────────────────────────────────── static_assert(sizeof(int) == 4, "int must be 4 bytes"); static_assert(std::is_trivially_copyable_v<MyType>, "Must be trivially copyable"); // ── SCOPED ENUMS ───────────────────────────────────────── enum class Color { Red, Green, Blue }; // Scoped — no implicit int conversion Color c = Color::Red; // int x = c; ERROR — no implicit conversion int x = static_cast<int>(c); // Must be explicit enum class Flags : uint8_t { None=0, Read=1, Write=2, Exec=4 }; // ── TYPE ALIASES ───────────────────────────────────────── using Vec2D = vector<vector<int>>; using Callback = std::function<void(int)>; // ── DELEGATING CONSTRUCTORS ────────────────────────────── // (shown in OOP file — recap: Foo() : Foo(0, 0) {}) // ── OVERRIDE / FINAL ───────────────────────────────────── // (shown in OOP file — recap: prevents silent hiding) // ── RVALUE REFS & MOVE SEMANTICS ───────────────────────── // (shown in OOP file — recap: T&& x = std::move(obj))

8.2   C++14 & C++17 — Quality of Life Modern

// ── C++14 ─────────────────────────────────────────────── // Generic lambdas (auto parameters) auto square = [](auto x) { return x * x; }; square(3); square(3.5); square(complex<double>{1,2}); // Return type deduction for regular functions auto multiply(int a, int b) { return a * b; } // return type deduced as int // Binary literals + digit separators int mask = 0b1111'0000; // Binary literal long big = 1'000'000'000; // Digit separator (visual only, ignored by compiler) // std::make_unique (finally added in C++14) auto p = std::make_unique<MyClass>(args...); // ── C++17 ─────────────────────────────────────────────── // Structured bindings — destructure pairs, tuples, structs auto [min_it, max_it] = minmax_element(v.begin(), v.end()); auto [key, val] = *myMap.begin(); // Unpack map entry auto [x, y, z] = myPoint; // Unpack struct // if / switch with initializer if (auto it = myMap.find("key"); it != myMap.end()) { cout << it->second; // it only in scope inside the if block } // std::optional — nullable value without pointer #include <optional> std::optional<int> findIndex(vector<int>& v, int target) { for (int i = 0; i < v.size(); i++) if (v[i] == target) return i; // Return value return std::nullopt; // Return "nothing" } auto idx = findIndex(v, 42); if (idx) cout << "Found at " << *idx; // * dereferences optional idx.value_or(-1); // 42 if found, -1 otherwise // std::variant — type-safe union #include <variant> std::variant<int, double, string> v; v = 42; cout << std::get<int>(v); // 42 v = 3.14; cout << std::get<double>(v); // 3.14 v = "hello"; cout << std::get<string>(v); // hello std::holds_alternative<int>(v); // false (it's string now) // std::string_view — non-owning string reference (no copy) #include <string_view> void process(std::string_view sv) { // Accepts string, const char*, literal — no copy cout << sv.substr(0, 3); } process("hello world"); // No allocation! process(myString); // View into myString — no copy // Fold expressions (C++17 — shown in templates chapter) // constexpr if — compile-time branching template<typename T> void process(T val) { if constexpr (std::is_integral_v<T>) { // Compiled away if false cout << "Integer: " << val << "\n"; } else if constexpr (std::is_floating_point_v<T>) { cout << "Float: " << fixed << setprecision(2) << val << "\n"; } else { cout << "Other: " << val << "\n"; } }

8.3   C++20 — The Next Big Leap Cutting Edge

// ── CONCEPTS (shown in templates chapter) ─────────────── // ── RANGES (shown in algorithms chapter) ──────────────── // ── COROUTINES ─────────────────────────────────────────── #include <coroutine> // Coroutines allow suspending/resuming functions // Key keywords: co_await, co_yield, co_return // Used for async I/O, generators, cooperative multitasking generator<int> fibonacci() { // Generator coroutine int a = 0, b = 1; while (true) { co_yield a; // Suspend, yield value, resume on next iteration int next = a + b; a = b; b = next; } } // ── MODULES ────────────────────────────────────────────── // Replaces #include — faster compilation, no header guard needed // module mylib; // module declaration // export module mylib; // export module // import mylib; // use it // ── THREE-WAY COMPARISON <=> ──────────────────────────── // (shown in operator overloading chapter) // ── std::span — non-owning contiguous range view ───────── #include <span> void process(std::span<int> data) { // Works with array, vector, raw ptr — no copy for (int x : data) cout << x << " "; } int arr[] = {1, 2, 3}; vector<int> vec = {4, 5, 6}; process(arr); // View into C array process(vec); // View into vector // ── std::format ────────────────────────────────────────── #include <format> cout << std::format("Hello, {}! You are {} years old.\n", "Alice", 25); cout << std::format("{:>10.2f}", 3.14159); // Right-align, 2 decimal places // ── Designated initializers ────────────────────────────── struct Point { int x, y, z; }; Point p = {.x = 1, .y = 2, .z = 3}; // Named members — clearer than {1,2,3} // ── consteval — must be evaluated at compile time ───────── consteval int compileTimeSquare(int n) { return n * n; } constexpr int val = compileTimeSquare(5); // 25, computed at compile time

8.4   Modern C++ Traps & Key Points

FeatureTrapCorrect Use
autoauto s = "hello"; — deduced as const char*, NOT std::stringstring s = "hello"; or auto s = string{"hello"};
Structured bindingsauto [a,b] = pair; — a,b are copies of the pair membersauto& [a,b] = pair; — references to avoid copies
string_view lifetimestring_view sv = myFunc(); — if function returns temp string, sv is danglingstring_view only valid as long as the underlying string lives
optional dereference without check*opt when opt is empty — undefined behaviorif (opt) or opt.has_value() before dereferencing
variant bad_variant_accessget<int>(v) when v holds double — throws bad_variant_accessUse holds_alternative<T>(v) first, or std::visit
↑ back to top
Chapter 09
Concurrency Primer — Threads, Mutexes & Async

9.1   std::thread — Creating Threads Advanced

#include <thread> #include <mutex> #include <future> // Create a thread — pass a callable and its arguments void worker(int id) { cout << "Thread " << id << " running\n"; } std::thread t1(worker, 1); // Starts immediately std::thread t2(worker, 2); std::thread t3([]{ cout << "Lambda thread\n"; }); t1.join(); // Wait for t1 to finish — MUST join or detach t2.join(); t3.join(); // detach() — thread runs independently (fire and forget) // WARNING: if main() ends while detached thread is running → UB std::thread t4(worker, 4); t4.detach(); // Dangerous — only for truly independent background work // Hardware concurrency — how many cores? unsigned int cores = std::thread::hardware_concurrency();

9.2   Mutex & lock_guard — Protecting Shared Data Critical

#include <mutex> int sharedCounter = 0; std::mutex mtx; // The lock void increment(int times) { for (int i = 0; i < times; i++) { // BAD: mtx.lock(); sharedCounter++; mtx.unlock(); // If exception between lock and unlock → deadlock forever! // GOOD: lock_guard — RAII mutex lock std::lock_guard<std::mutex> lock(mtx); // Locks here sharedCounter++; } // lock destroyed here → mutex automatically unlocked } std::thread t1(increment, 1000); std::thread t2(increment, 1000); t1.join(); t2.join(); cout << sharedCounter << "\n"; // Always 2000 — race condition prevented // unique_lock — more flexible than lock_guard std::unique_lock<std::mutex> uLock(mtx); // Lock on construction uLock.unlock(); // Manually unlock (lock_guard can't) uLock.lock(); // Re-lock // Needed for condition variables and timed operations // shared_mutex (C++17) — reader-writer lock #include <shared_mutex> std::shared_mutex rwMutex; // Multiple readers: std::shared_lock<std::shared_mutex> rLock(rwMutex); // Single writer: std::unique_lock<std::shared_mutex> wLock(rwMutex);
Deadlock — Threads Waiting Forever

Deadlock occurs when thread A holds mutex 1 and waits for mutex 2, while thread B holds mutex 2 and waits for mutex 1. Prevention rules: always lock multiple mutexes in the same order, or use std::lock(m1, m2) which locks both atomically. Never hold a lock while calling user code that might try to acquire the same lock.

9.3   std::async & std::future — High-Level Async Preferred

#include <future> int heavyComputation(int n) { // Simulate long work std::this_thread::sleep_for(std::chrono::seconds(2)); return n * n; } // Launch async task — returns a future auto fut = std::async(std::launch::async, heavyComputation, 10); // Do other work while heavy computation runs in background... cout << "Doing other stuff...\n"; // Get result — blocks until ready int result = fut.get(); // 100 cout << "Result: " << result << "\n"; // Promise + Future — manual result passing between threads std::promise<int> prom; std::future<int> fut2 = prom.get_future(); std::thread worker([&prom] { prom.set_value(42); // Fulfill the promise from worker thread }); cout << fut2.get() << "\n"; // 42 — main waits here worker.join(); // std::atomic — lock-free operations for simple types #include <atomic> std::atomic<int> atomicCount{0}; atomicCount++; // Thread-safe without mutex! atomicCount.fetch_add(5); // Atomic add, returns old value atomicCount.load(); // Atomic read atomicCount.store(100); // Atomic write

9.4   Concurrency Traps & Key Points

TrapProblemFix
Data raceTwo threads read/write shared var without mutex — UBProtect with mutex or use atomic<T>
Forgetting to join/detachstd::thread destructor called on joinable thread → std::terminateAlways call join() or detach() before thread goes out of scope
DeadlockLock same mutex twice in one thread / circular lock orderLock in consistent order; use std::lock() for multiple; avoid nested locks
Spurious wakeupscondition_variable::wait wakes up without being notifiedAlways use predicate overload: cv.wait(lock, []{ return ready; })
Race on shared_ptr refcountSharing shared_ptr between threads without protectionshared_ptr refcount is atomic but object access still needs mutex
↑ back to top
Chapter 10
Traps & Master Reference — The Advanced C++ Cheatsheet

10.1   Complete STL Header Reference Cheatsheet

HeaderContents
<vector>vector<T>
<array>array<T,N> — fixed-size stack array with STL interface
<deque>deque<T> — double-ended queue
<list>list<T> — doubly-linked list
<forward_list>forward_list<T> — singly-linked list
<map>map<K,V>, multimap<K,V>
<unordered_map>unordered_map<K,V>, unordered_multimap<K,V>
<set>set<T>, multiset<T>
<unordered_set>unordered_set<T>, unordered_multiset<T>
<stack>stack<T>
<queue>queue<T>, priority_queue<T>
<algorithm>sort, find, count, transform, reverse, unique, lower_bound…
<numeric>accumulate, iota, partial_sum, inner_product, reduce
<memory>unique_ptr, shared_ptr, weak_ptr, make_unique, make_shared
<functional>function<>, bind, placeholders, hash, less, greater
<string>string, wstring, to_string, stoi, stod
<string_view>string_view, wstring_view (C++17)
<optional>optional<T>, nullopt (C++17)
<variant>variant<Ts...>, get, holds_alternative, visit (C++17)
<any>any, any_cast (C++17) — type-erased single value
<span>span<T> — non-owning view (C++20)
<ranges>ranges algorithms, views (C++20)
<thread>thread, this_thread::sleep_for, hardware_concurrency
<mutex>mutex, lock_guard, unique_lock, scoped_lock
<future>future, promise, async, packaged_task
<atomic>atomic<T>, atomic_flag
<chrono>duration, time_point, steady_clock, system_clock
<type_traits>is_integral, is_pointer, remove_reference, enable_if…
<concepts>integral, floating_point, same_as, convertible_to… (C++20)

10.2   Advanced C++ Quick-Reference Cards Reference

Smart Pointer Choice

Sole owner → unique_ptr
Shared owner → shared_ptr
Observer/cycle → weak_ptr
Always: make_unique/make_shared
Never: raw new/delete

Lambda Capture Cheatsheet

[] — capture nothing
[=] — all by copy
[&] — all by reference
[x, &y] — x copy, y ref
[this] — this pointer
[*this] — copy of object (C++17)

STL Complexity

vector random access: O(1)
vector push_back (amortized): O(1)
map/set insert/find: O(log n)
unordered_map find: O(1) avg
sort: O(n log n)
binary_search: O(log n)

Modern C++ Per-Version

C++11: auto, lambda, move, nullptr, enum class, unique_ptr
C++14: generic lambda, make_unique
C++17: structured binding, optional, variant, string_view, if-init
C++20: concepts, ranges, format, span, coroutines

Thread Safety Primitives

mutex — exclusive lock
lock_guard — RAII lock (no unlock)
unique_lock — RAII + manual unlock
shared_mutex — reader-writer
atomic<T> — lock-free simple types
condition_variable — wait/notify

Exception Safety Levels

None — corruption possible
Basic — no leak, valid state
Strong — commit or rollback
No-throw — never throws
RAII gives Basic for free.
copy-and-swap gives Strong.

10.3   Top Advanced C++ Bugs Must Know

#BugSymptomFix
1Iterator invalidationpush_back after storing iterator → crashreserve() upfront or use indices
2map[] on readSilent insertion of default valueUse find() or count() for read-only access
3Dangling lambda capture[&x] after x destroyed → UB[x] copy capture, or ensure lambda lifetime < captured var
4Data raceIntermittent wrong results, crashesmutex / lock_guard around all shared mutable state
5Cyclic shared_ptrMemory never freed, growing leakBreak cycle with weak_ptr
6unique on unsortedOnly removes consecutive duplicatessort → unique → erase
7Throwing in destructorstd::terminate if second exception activeCatch all exceptions in destructor; never propagate
8Missing noexcept on movevector uses copy instead of move on resizeMark move ctor/assign noexcept
9string_view danglingstring_view into destroyed temporary stringstring_view only for function params / temporaries in same expression
10Thread not joined/detachedstd::terminate on thread destructorAlways join() or detach() before thread goes out of scope
↑ back to top