# C++ advanced: _under the hood_ You've written classes, managed resources with RAII, and leaned on the standard containers.[1](#ref-1) Now we go underneath. C++'s power comes from three ideas working together: **generics** (write one algorithm for every type), **moves** (transfer resources instead of copying them), and **concurrency** (do several things at once, safely). The whole standard library is built on these. As always, every feature below links to cppreference.com[2](#ref-2) or the C++ Core Guidelines.[3](#ref-3) You should be comfortable with Parts 1 and 2 first.[1](#ref-1)[4](#ref-4) Type each example. The project at the end uses _all_ of it. ## 01Templates: write once, work for any type A **template** is a recipe the compiler uses to generate code for whatever type you plug in. A _function template_ deduces its type from the arguments:[5](#ref-5) ``` template T max_of(T a, T b) { return (a > b) ? a : b; } int i = max_of(3, 7); // T deduced as int double d = max_of(2.5, 1.8); // T deduced as double ``` A _class template_ parameterises a whole type, `std::vector` is one. You can constrain templates with **concepts** (C++20) so errors are readable instead of pages of template gibberish:[6](#ref-6) ``` template requires std::integral // T must be an integer type T half(T x) { return x / 2; } std::cout << half(10); // OK: int is integral // half(3.0); // error: double is not integral ``` The Core Guidelines treat templates as the default way to express "the same logic, many types", prefer them over copying code or reaching for macros.[3](#ref-3) ## 02The library Before writing a loop by hand, check whether `` already does it. These are generic, tested, and usually faster than a hand-rolled loop. They operate on _iterator pairs_, the half-open range `[begin, end)`.[7](#ref-7) ``` #include #include std::vector v = {5, 2, 9, 1, 5}; std::sort(v.begin(), v.end()); // 1 2 5 5 9 auto it = std::find(v.begin(), v.end(), 9); // iterator to 9 bool has = (it != v.end()); int sum = 0; std::for_each(v.begin(), v.end(), [&](int x) { sum += x; }); ``` Iterators are what make templates and algorithms compose: an algorithm written for `vector` works for `array`, `map`, or even a raw pointer range, because they all speak the same iterator "language." Prefer the named algorithm over a raw loop, it states your _intent_.[3](#ref-3) ## 03Lambdas: functions on the spot A **lambda** is an unnamed function you write inline, ideal as the "what to do" argument to an algorithm. The `[…]` is the _capture list_: `[&]` captures variables by reference, `[=]` by value.[8](#ref-8) ``` std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // descending int threshold = 4; auto count = std::count_if(v.begin(), v.end(), [threshold](int x) { return x > threshold; }); // captures by value ``` By default a lambda can't modify captured-by-value variables; add `mutable` to allow it. To store a lambda in a variable, give it type `std::function`.[8](#ref-8) Lambdas are the modern replacement for many hand-written functors, and they're how you make algorithms expressive. ## 04Move semantics: transfer, don't copy Here's the key advanced idea. A copy duplicates data; a **move** takes ownership of it. When you return a big object or pass a temporary, C++ can "steal" its contents instead of cloning them, dramatically faster for strings, vectors, and containers.[9](#ref-9) An **rvalue reference** (`&&`) binds to temporaries. `std::move(x)` is a cast that says "I'm done with x, please take its guts."[10](#ref-10) ``` std::vector make_big() { std::vector v(1 << 20); // a million ints return v; // moved, not copied } std::vector a = make_big(); // the data is transferred std::vector b = std::move(a); // a is now empty; b owns the data ``` Understanding lvalues vs rvalues, and when the compiler chooses a move constructor over a copy constructor, is what separates advanced C++ from intermediate.[11](#ref-11) Pair it with `std::forward` in generic code to preserve whether an argument was an lvalue or rvalue ("perfect forwarding").[12](#ref-12) ## 05The rule of zero (and five) In Part 2 you learned RAII: tie a resource to an object's lifetime so it's cleaned up automatically.[13](#ref-13) The modern conclusion is the **rule of zero**: if your class only holds smart pointers, containers, and strings, all of which already manage themselves, then _you write no destructor, copy, or move code at all_. The compiler generates correct ones.[3](#ref-3) ``` struct Document { // rule of zero std::string title; std::vector data; std::unique_ptr note; // owns a heap object }; // copies, moves, and cleanup: all correct, for free ``` Only when you _directly_ own a raw resource (a file handle, a `new`ed pointer, a lock) do you fall back to the **rule of five**: define (or `= default`/`= delete`) the destructor, copy/move constructors, and copy/move assignment, as a set.[14](#ref-14) In practice: prefer the rule of zero; reach for `std::unique_ptr` so you almost never need the rule of five at all.[3](#ref-3) ## 06Concurrency with std:thread C++11 gave us portable threads in the standard library. A `std::thread` runs a function concurrently with the rest of your program.[15](#ref-15) You must `join()` (wait for it to finish) or `detach()` (let it run free) before it's destroyed, RAII saves you here too, via `std::jthread` in C++20, which joins automatically. ``` #include #include void work(int id) { std::cout << "thread " << id << " running\n"; } std::thread t1{work, 1}; std::thread t2{work, 2}; t1.join(); // wait for t1 t2.join(); // wait for t2 ``` ## 07Sharing data safely: races and atomics Threads sharing a variable without coordination produce **data races**: undefined behaviour. The simplest fix for a single counter is `std::atomic`, which makes reads/writes indivisible:[16](#ref-16) ``` #include std::atomic counter{0}; void bump() { for (int i = 0; i < 1000; i++) counter++; } std::thread a{bump}, b{bump}; a.join(); b.join(); std::cout << counter << std::endl; // reliably 2000 ``` For richer shared state you'd use a `std::mutex` (lock with `std::lock_guard`) so only one thread enters a critical section at a time. The Core Guidelines are strict: never share writable state between threads without a synchronisation mechanism, and prefer the highest-level tool that works (atomics > mutex > hand-rolled flags).[3](#ref-3) ## 08Project: a parallel word-frequency counter Let's combine everything: templates, algorithms, lambdas, RAII, and threads. We'll read a file, split it into chunks, count word frequencies in parallel with `std::async` (which manages threads for us), then merge the results. ``` #include #include #include #include #include #include #include using Freq = std::map; // count words in one chunk of text (pure, easy to run on a thread) Freq count_in(const std::string& text) { Freq f; std::istringstream in(text); std::string word; while (in >> word) f[word]++; return f; // returned by move: no copy of the map } int main() { std::ifstream in{"sample.txt"}; std::string chunk, all; while (std::getline(in, chunk)) all += chunk + " "; // split roughly in half and count each half on its own thread auto mid = all.size() / 2; std::string a = all.substr(0, mid); std::string b = all.substr(mid); auto fa = std::async(std::launch::async, count_in, std::cref(a)); auto fb = std::async(std::launch::async, count_in, std::cref(b)); Freq merged = fa.get(); // wait + move result for (const auto& [w, n] : fb.get()) // structured bindings (C++17) merged[w] += n; for (const auto& [w, n] : merged) std::cout << w << ": " << n << "\n"; return 0; } ``` Read it back: `std::async` spawns threads and hands you a `std::future`; `.get()` waits for the result and moves it out (no copying the map). `std::cref` passes the string by `const` reference into the async task so we don't clone the whole text. That's advanced C++ in about thirty lines, generic helpers, concurrency, moves, and RAII all cooperating. ## 09Where to go next You've now seen the full arc: from "hello, world" to templates, the algorithm library, lambdas, move semantics, the rule of zero, and real threads. These are the tools professional C++ is built from. - **Keep the standard library central.** Templates + algorithms + containers solve most problems with less code and fewer bugs than hand-rolled loops and raw memory.[3](#ref-3) - **Think in ownership.** "Who owns this resource, and when is it freed?", if the answer is a smart pointer or a container, you're following the rule of zero and sleeping well at night.[13](#ref-13) - **Be careful with threads.** Shared mutable state is the hardest part of the language; prefer `std::async` and atomics over manual locking until you have a reason not to.[3](#ref-3) - **Read the sources.** [cppreference.com for the exact signature; the C++ Core Guidelines for "what should I do?"; learncpp.com](https://en.cppreference.com/w/) for a patient, example-driven tour of every feature above.[2](#ref-2)[3](#ref-3)[17](#ref-17) > **About this piece.** This is Part 3 (Advanced), and the finale, of a three-part C++ series from OcxlyDev. It follows [Part 1: Basics](cpp-basics.html) and [Part 2: Intermediate](cpp-intermediate.html). Every language feature links to a reputable primary source below (cppreference.com, the C++ Core Guidelines, and learncpp.com). You now have the full toolkit, go build something. ## References - Bjarne Stroustrup & Herb Sutter — C++ Core Guidelines (P.1, F.1, R.1, ES.28, C.20, C.21, CP.*) - cppreference.com — C++ reference (definitive language and standard-library documentation) - C++ Core Guidelines — modern best-practice rules for templates, resource safety, and concurrency - learncpp.com — free, modern C++ tutorial (intermediate and advanced chapters) - cppreference.com — Function templates (type deduction from arguments) - cppreference.com — Constraints and concepts (C++20 template constraints, std::integral) - cppreference.com — <algorithm> (std::sort, std::find, std::for_each, std::count_if; iterator ranges) - cppreference.com — Lambda expressions (capture lists, mutable, std::function) - cppreference.com — Reference declaration (lvalue/rvalue references; the basis of move semantics) - cppreference.com — std::move (cast to rvalue reference; enabling moves) - cppreference.com — Move constructor and move assignment (transferring resources) - cppreference.com — std::forward (perfect forwarding of lvalue/rvalue) - cppreference.com — RAII (Resource Acquisition Is Initialization; automatic cleanup via scope) - cppreference.com — Rule of three/five/zero (when to define destructor and copy/move operations) - cppreference.com — std::thread (launching and joining concurrent execution) - cppreference.com — std::atomic (lock-free, race-free shared counters) - learncpp.com — exhaustive lessons on templates, lambdas, move semantics, and multithreading