Before You Start
Bjarne Stroustrup's 1979 bet — C's performance with real object-oriented abstraction — still defines the language today. These two lessons explain why, and get a compiler installed.
What is C++?
Bjarne Stroustrup started with 'C with Classes' in 1979
Stroustrup's original goal was C's raw performance combined with real object-oriented abstraction — a bet that classes and zero-cost abstraction could coexist with C's closeness to the machine. That same tension between abstraction and control still shapes every design decision C++ makes today.
C++'s origin as an extension of C, the object-oriented abstraction Stroustrup added, and where C++23 sits in that history.
Setup and Compiler
GCC, Clang, and MSVC — three genuinely different compilers
Unlike languages with one reference implementation, C++ has three real, independently maintained compilers in everyday use — GCC, Clang, and MSVC — each adopting new standard features at its own pace. Getting one installed and a first file compiled is this lesson's entire goal.
Installing GCC/Clang/MSVC, compiling and running a first program, and basic compiler flags for standard version.
Language Fundamentals
C++ is fully statically typed like C, but with real additions on top: type inference, overloading, references instead of raw pointers, and namespaces to keep large codebases sane. Five lessons before classes properly begin.
Variables and Types
auto infers a type at compile time — C++ stays fully statically typed
The <code>auto</code> keyword saves you from writing out a long type by hand, but it changes nothing about C++'s static typing: the compiler still determines and locks in a concrete type at compile time, it just infers it from the initializer instead of requiring you to spell it out.
Fundamental types, auto's type-inference rules, const, and the static typing C++ keeps even with auto.
Control Flow
The range-based for loop eliminated most manual index bookkeeping
C++11's range-based <code>for</code> loop lets you iterate a container's elements directly, without hand-managing an index or an iterator — eliminating an entire category of off-by-one bugs that plagued C-style loops for decades.
if/else, while/do-while, the classic indexed for loop, and the range-based for loop introduced in C++11.
Functions
Overloading and default arguments — real C++ additions C never had
Function overloading (multiple functions sharing a name, distinguished by parameter types) and default arguments are both genuine C++ additions with no C equivalent — C requires a separately named function for each variant instead. Both features rely on the compiler resolving the right call at compile time.
Function declarations, overloading and its resolution rules, default arguments, and inline functions.
References
An alias for an existing variable — not a pointer, can't be null
A C++ reference is not a pointer wearing a different syntax: it is a genuine alias for an existing variable, it cannot be reseated to refer to something else after initialization, and by the language's own rules it cannot be null. These constraints are exactly what makes references safer to pass around than raw pointers.
Reference declaration and initialization, reference parameters, const references, and how references differ from pointers.
Namespaces
Two libraries can each define Vector with zero conflict
Namespaces let two entirely unrelated libraries each define a class named <code>Vector</code> without any collision, as long as each lives inside its own namespace — the mechanism that makes large C++ codebases and third-party libraries safely composable.
Namespace declarations, nested namespaces, using-declarations vs. using-directives, and the std namespace.
Classes, RAII and Memory — C++'s Defining Discipline
This is the stage that makes C++, C++: real object-oriented classes wired to automatic resource management, the memory model underneath it, and the move semantics that make it fast — then the last lesson ties every piece together into one realistic class. Seven lessons, the heart of the course.
Inheritance and Polymorphism
Methods are NOT virtual by default — forgetting the keyword silently breaks it
Unlike Java, a C++ method must be explicitly marked <code>virtual</code> to participate in dynamic dispatch — forget the keyword and a call through a base-class pointer silently resolves to the base class's version instead of the derived override, with no compiler error at all.
Base and derived classes, the virtual keyword and dynamic dispatch, pure virtual functions, abstract classes, and override.
Operator Overloading
a + b can call your own code — exactly how std::string and std::vector work
Operator overloading lets a custom type respond to <code>+</code>, <code>==</code>, <code><<</code> and the rest with your own code, resolved at compile time by argument types — the exact same mechanism the standard library itself uses to make <code>std::string</code> concatenation and stream output work.
Overloading arithmetic, comparison, and stream operators, member vs. free-function overloads, and common conventions.
Multiple Inheritance
A class can genuinely extend two classes — and the diamond problem follows
C++ genuinely allows a class to inherit from two (or more) base classes at once, unlike languages that restrict inheritance to a single parent. The famous diamond problem — two paths to a shared ancestor producing ambiguous, duplicated state — is the real complication that follows, and virtual inheritance is C++'s specific answer to it.
Multiple base classes, the diamond problem, virtual inheritance, and when multiple inheritance is (and isn't) the right tool.
Memory Management
new and delete replace malloc/free — modern style barely uses either directly
<code>new</code> and <code>delete</code> replace C's <code>malloc</code>/<code>free</code> and also run constructors and destructors, but idiomatic modern C++ style says to barely ever call either one directly — that responsibility belongs to RAII types and smart pointers instead, which the next two lessons cover.
new/delete vs. malloc/free, constructor/destructor invocation, memory leaks, and why direct new/delete is now considered a smell.
Smart Pointers
unique_ptr and shared_ptr wrap new/delete in RAII
<code>std::unique_ptr</code> and <code>std::shared_ptr</code> wrap raw <code>new</code>/<code>delete</code> calls inside RAII, automatically freeing memory when the smart pointer itself goes out of scope. Modern C++ style barely calls <code>new</code> or <code>delete</code> directly anymore because of exactly these two types.
unique_ptr and its move-only ownership, shared_ptr and reference counting, weak_ptr for breaking cycles, and make_unique/make_shared.
Move Semantics
Instead of copying a huge vector, C++11 lets you steal its buffer
Move semantics, introduced in C++11, let you transfer — 'steal' — a large object's internal buffer instead of copying it, leaving the original in a valid-but-empty state while the operation itself costs nearly nothing. This is the single biggest performance idea C++11 added to the language.
Rvalue references (&&), std::move, move constructors and move assignment, and how they interact with the rule of five.
Classes and RAII, Tied Together
Constructors, destructors, smart pointers, the rule of five, and move semantics together
This lesson is the synthesis for the stage: class definition, constructors and destructors, RAII as the unifying idea, smart pointers, the rule of five, and move semantics — combined into the actual discipline experienced C++ code follows for resource management. Read it once each piece is familiar on its own.
Class definitions, constructors/destructors, RAII, smart pointers, the rule of five (and rule of zero), and move semantics together.
Templates and the STL
Generic code and the Standard Template Library — how C++ gets type-safe reusability, the containers you'll reach for constantly, and the concepts/lambdas that make templates usable in practice. Five lessons.
The STL Containers
vector should be your default — reach for anything else with a specific reason
<code>std::vector</code> is the correct default container for nearly every job in C++, with contiguous storage and cache-friendly access; reaching for <code>list</code>, <code>map</code>, or anything else should be a deliberate choice justified by a specific access pattern, not a habit.
vector, array, deque, list, map/unordered_map, set/unordered_set, and choosing between them by access pattern.
Lambda Expressions
You must explicitly choose how each outer variable is captured
Unlike Python or JavaScript closures, a C++ lambda forces you to explicitly state how it captures each outer-scope variable — by copy or by reference — rather than capturing everything implicitly. This explicitness is exactly what prevents a whole category of dangling-reference bugs in captured lambdas.
Lambda syntax, capture lists ([=], [&], and named captures), mutable lambdas, and lambdas as function arguments.
Concepts and Constraints
Before C++20, a bad template argument produced unreadable errors
A template argument that didn't satisfy what a template actually required used to produce hundreds of lines of near-unreadable compiler error, often pointing deep inside library internals rather than at the actual mistake. C++20 concepts finally let a template state its real requirements directly, turning that wall of text into a clear, specific error message.
Defining and using concepts, requires clauses, standard concepts (std::integral, std::copyable), and how they improve error messages.
The STL Algorithms
std::sort, std::find, std::transform replace hand-written loops
The <code><algorithm></code> header's functions — <code>std::sort</code>, <code>std::find</code>, <code>std::transform</code> and dozens more — replace hand-written loops with calls that are tested, typically better-optimized than a hand-rolled version, and self-documenting about intent.
std::sort, std::find, std::transform, std::accumulate, and iterators as the glue between containers and algorithms.
Templates and the STL, Tied Together
Function and class templates, specialisation, containers, iterators and algorithms together
This lesson is the synthesis for the stage: function and class templates, template specialisation, how the STL containers from earlier in this stage are themselves built from templates, iterators as the uniform interface between containers and algorithms, and the algorithms themselves — the whole generic-programming picture in one place.
Function templates, class templates, template specialisation, STL containers recap, iterators, and STL algorithms together.
Errors, Concurrency and Modern Types
What happens when things go wrong, what runs at the same time as what, and the newer standard types built to replace older, riskier patterns. Five lessons.
Exceptions
Stack unwinding calls every local object's destructor automatically
When a C++ exception propagates, stack unwinding automatically calls the destructor of every local object that goes out of scope along the way — which is exactly why RAII and exceptions work so well together: cleanup happens whether a function returns normally or throws.
try/catch/throw, exception hierarchies, stack unwinding and destructor calls, and exception safety guarantees.
The Standard Library
std::optional, std::variant, std::expected replace raw pointers and unions
<code>std::optional</code>, <code>std::variant</code>, and C++23's <code>std::expected</code> each replace a pattern that used to require raw pointers, unions, or exceptions for the exact same job — representing absence, one-of-several-types, or an expected failure, respectively, with real type safety.
std::optional for absence, std::variant for tagged unions, std::expected for fallible operations, and std::string_view.
Undefined Behaviour
C++ inherits C's UB, then adds its own C++-specific traps
C++ inherits every one of C's undefined-behaviour categories wholesale, then adds genuinely new ones on top — an invalidated iterator used after a container reallocates, or a virtual function called from inside a constructor before the derived class even exists, are two real, C++-specific traps worth knowing by name.
UB inherited from C, iterator invalidation, calling virtual functions during construction/destruction, and sanitizer tools.
Coroutines
co_await/co_yield shipped with almost no ready-to-use types
C++20 added the <code>co_await</code> and <code>co_yield</code> keywords for suspending and resuming a function's execution, but shipped the language feature almost entirely without the concrete coroutine types needed to actually use it — those largely arrived later from third-party libraries, a genuinely unusual way for a major language feature to ship.
co_await, co_yield, co_return, the promise-type machinery underneath them, and why library support lagged the language feature.
Concurrency
Destroying a running std::thread without join() terminates the whole program
Destroying a <code>std::thread</code> object while its thread is still running, without first calling <code>join()</code> or <code>detach()</code>, calls <code>std::terminate</code> and kills the entire program — a sharp-edged default that <code>std::jthread</code>, added in C++20, finally fixes by joining automatically in its own destructor.
std::thread and the join-or-terminate rule, mutexes and locks, std::atomic, and std::jthread's automatic join.
Tooling, Standards and Modern C++
What surrounds the code: the preprocessor C++ inherited, the build systems and test frameworks that hold a real project together, the standards history, and a final lesson tying the whole modern-C++ toolkit together. Five lessons to close out the course.
The Preprocessor
Templates and constexpr replace most of what macros used to be needed for
C++ inherited C's text-substitution preprocessor wholesale, but templates and <code>constexpr</code> now cover most of the jobs macros used to be the only tool for — type-safe generic code and compile-time computation, without a macro's total lack of type checking or scoping.
#define and #include recap, conditional compilation, and why templates/constexpr are preferred over macros where they overlap.
Build Systems
CMake generates build files — it doesn't compile anything itself
CMake is not a compiler or even a build tool in the direct sense: it generates the actual build files — a Makefile, a Ninja file, a Visual Studio project — for whatever tool your platform and toolchain actually prefer, which is exactly why the same CMakeLists.txt works across GCC, Clang, and MSVC alike.
CMakeLists.txt basics, targets and dependencies, generating and invoking the underlying build tool, and package management options.
Testing
No built-in test runner — Google Test and Catch2 are what people actually use
C++ ships no standard test runner at all, unlike languages with one built into the toolchain. Google Test and Catch2 are the two frameworks nearly every real C++ codebase reaches for instead, each with genuinely different assertion styles and setup conventions worth knowing both of.
Google Test's TEST macros and fixtures, Catch2's alternative assertion style, and organizing a real C++ test suite.
C++ Standards History
C++11 was such an overhaul it's called a second birth for the language
C++11 added so much at once — auto, lambdas, move semantics, the range-based for loop, smart pointers — that it is routinely called a second birth for the language, with C++14/17/20/23 each building incrementally on that one foundational release rather than any single one matching its scope.
Pre-C++11 C++, the C++11 overhaul, and the incremental additions in C++14, C++17, C++20, and C++23.
Modern C++, Tied Together
auto, lambdas, move semantics, constexpr, concepts, ranges and coroutines together
This lesson is the course's synthesis and capstone: <code>auto</code>, lambdas, move semantics, <code>constexpr</code>, structured bindings, <code>std::optional</code>, concepts, ranges, and coroutines — the individual modern-C++ features from across this entire course, seen together as the toolkit that actually defines what 'writing modern C++' means in 2026.
auto, lambdas, move semantics, constexpr, structured bindings, std::optional, concepts, ranges, and coroutines, together.
Concepts That Cross Languages
These pages explain ideas that are not specific to C++ — they apply to every language you will ever learn. Read them once and the next language costs you far less effort. They pair well with the lessons above rather than replacing them.
Build Real Things — 10 Projects
Reading is not enough. These 10 projects — five basic, five medium — are chosen because each one exercises something specific you learned above, and because they are genuinely idiomatic C++ rather than generic exercises. Each link below opens the project brief; the C++ implementation walkthrough is in progress.
std::cin input parsing, a guess loop, and <random> for the secret number.
A small switch or if-chain for conversion direction, and iostream formatting.
Reading a file with std::ifstream, and an unordered_map<std::string,int> for a frequency table.
A struct for the task shape, a std::vector to hold them, and hand-rolled file persistence.
Flag-based character-set selection, and <random>'s engines versus the security pitfalls of rand().
A raw socket and hand-written HTTP request, since the standard library ships no HTTP client at all.
A minimal HTTP server over raw sockets, with std::variant-based typed responses and manual JSON formatting.
A std::variant for subcommands, and std::visit for exhaustive-by-convention dispatch.
An unordered_map-backed store with a std::mutex for thread safety, and std::jthread for the server loop.
Line-by-line parsing with std::istringstream, and STL algorithms for top-N aggregation.
Practice & Experimentation
Ongoing, not a final step. Use these throughout the course — try every snippet you read, and run anything you are unsure about rather than assuming.
Run C++ code without installing anything, via the official C++ Playground.
Copy-ready idiomatic patterns to keep beside you while you build.
All 29 topic pages in one index, for looking things up later.