OcxlyDev · Tutorial · Part 3 of 3

C++ advanced: under the hood

Templates, the STL algorithms, lambdas, move semantics, the rule of zero/five, and real threads. This is where C++ stops being "a harder C" and becomes the language its designers intended.

OcxlyDev Published 25 July 2026 ~30 min read Advanced Sources linked throughout

You've written classes, managed resources with RAII, and leaned on the standard containers.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.com2 or the C++ Core Guidelines.3 You should be comfortable with Parts 1 and 2 first.14

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

template <typename T>
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

template <typename T>
requires std::integral<T>      // 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

02The <algorithm> library

Before writing a loop by hand, check whether <algorithm> 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

#include <algorithm>
#include <vector>
std::vector<int> 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

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

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<Return(Args)>.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

An rvalue reference (&&) binds to temporaries. std::move(x) is a cast that says "I'm done with x, please take its guts."10

std::vector<int> make_big() {
    std::vector<int> v(1 << 20);   // a million ints
    return v;                       // moved, not copied
}

std::vector<int> a = make_big();   // the data is transferred
std::vector<int> 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 Pair it with std::forward in generic code to preserve whether an argument was an lvalue or rvalue ("perfect forwarding").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 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

struct Document {               // rule of zero
    std::string title;
    std::vector<char> data;
    std::unique_ptr<std::string> 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 newed 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 In practice: prefer the rule of zero; reach for std::unique_ptr so you almost never need the rule of five at all.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 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 <thread>
#include <iostream>

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

#include <atomic>
std::atomic<int> 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

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 <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <map>
#include <vector>
#include <future>

using Freq = std::map<std::string, int>;

// 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.

About this piece. This is Part 3 (Advanced), and the finale, of a three-part C++ series from OcxlyDev. It follows Part 1: Basics and Part 2: Intermediate. 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

  1. Bjarne Stroustrup & Herb Sutter — C++ Core Guidelines (P.1, F.1, R.1, ES.28, C.20, C.21, CP.*)
  2. cppreference.com — C++ reference (definitive language and standard-library documentation)
  3. C++ Core Guidelines — modern best-practice rules for templates, resource safety, and concurrency
  4. learncpp.com — free, modern C++ tutorial (intermediate and advanced chapters)
  5. cppreference.com — Function templates (type deduction from arguments)
  6. cppreference.com — Constraints and concepts (C++20 template constraints, std::integral)
  7. cppreference.com — <algorithm> (std::sort, std::find, std::for_each, std::count_if; iterator ranges)
  8. cppreference.com — Lambda expressions (capture lists, mutable, std::function)
  9. cppreference.com — Reference declaration (lvalue/rvalue references; the basis of move semantics)
  10. cppreference.com — std::move (cast to rvalue reference; enabling moves)
  11. cppreference.com — Move constructor and move assignment (transferring resources)
  12. cppreference.com — std::forward (perfect forwarding of lvalue/rvalue)
  13. cppreference.com — RAII (Resource Acquisition Is Initialization; automatic cleanup via scope)
  14. cppreference.com — Rule of three/five/zero (when to define destructor and copy/move operations)
  15. cppreference.com — std::thread (launching and joining concurrent execution)
  16. cppreference.com — std::atomic (lock-free, race-free shared counters)
  17. learncpp.com — exhaustive lessons on templates, lambdas, move semantics, and multithreading