C++ Basics — Memory, Performance, and Build Systems

Status: Active | Last Updated: 2026-08-29 Category: Languages — C++ Prerequisites: Basic programming concepts; memory model understanding strongly recommended Tags: cpp, memory, pointers, templates, cmake, build Estimated Time: 4-6 hours (Self-paced, includes lab time)

Summary

C++ is a compiled, statically typed language that gives you fine-grained control over memory, performance, and system resources. It introduces concepts — pointers, references, templates, RAII — that don't exist in modern high-level languages. This article covers C++ fundamentals, its build systems, and when C++ is the right choice.

What You'll Learn (Core Competencies)

Table of Contents

  1. Architectural Overview & Core Schema
  2. Deep Dive & Implementation
  3. Anti-Patterns & Common Pitfalls
  4. Independent Challenge
  5. Consolidation & Key Invariants
  6. Next Steps

1. Architectural Overview & Core Schema

Why C++

C++ is the language of choice when you need:

Unlike Rust, C++ does not enforce memory safety at the compiler level — you must use discipline (smart pointers, RAII, code reviews) to avoid leaks, buffer overflows, and use-after-free errors.

The Compilation Model

C++ compiles to native machine code through a multi-stage build pipeline. Modern projects use CMake to generate the actual build files (Makefiles, Ninja, Visual Studio projects, etc.):

Stage Tool Output
Source Editor *.cpp, *.hpp
Preprocessing Preprocessor Translation unit with macros expanded
Compilation g++/clang++ Object file (.o/.obj)
Linking Linker Executable or library
Build system CMake / Make / Ninja Coordinates the above

Unlike Python or Go, C++ requires an explicit build step before running. The compiler produces native machine code, so there is no VM or interpreter in production. The compiled binary depends on the C++ standard library (libstdc++ or libc++), which is dynamically linked by default.

Memory: The Foundation of C++

In C++, every value lives in either the stack (automatic, fast, limited) or the heap (manual, flexible, must be freed):

int a = 10;  // stack — freed when scope ends
int* b = new int(20);  // heap — you must call delete b;

A pointer (int*) holds a memory address. A reference (int&) is an alias to an existing variable — it must always refer to a valid object:

void increment(int& n) {
    n += 1;  // modifies the original variable
}

The golden rule: every new must have a matching delete, or use smart pointers (std::unique_ptr, std::shared_ptr) that delete automatically.

RAII: The C++ Superpower

RAII (Resource Acquisition Is Initialization) is C++'s most important pattern. The insight: wrap every resource in an object whose destructor frees it. When the object goes out of scope, the destructor runs — guaranteed, even through exceptions. This is how std::vector, std::fstream, std::lock_guard, and every standard container work.


2. Deep Dive & Implementation

Memory: Stack, Heap, Pointers

In C++, every variable lives in either the stack (automatic, fast, limited) or the heap (manual, flexible, must be freed):

int a = 10;  // stack — freed when scope ends
int* b = new int(20);  // heap — you must call delete b;

A pointer (int*) holds a memory address. A reference (int&) is an alias to an existing variable — it must always refer to a valid object:

void increment(int& n) {
    n += 1;  // modifies the original variable
}

The golden rule: every new must have a matching delete, or use smart pointers (std::unique_ptr, std::shared_ptr) that delete automatically.

Pointers vs References

int x = 10;
int* p = &x;   // pointer — holds address, can be reassigned
int& r = x;    // reference — alias, cannot be reassigned

r = 20;        // OK — modifies x
// r = &y;     // ERROR — cannot rebind reference

p = &x;        // OK — pointer can be reassigned
p = nullptr;   // OK — pointer can be null

// r = nullptr; // ERROR — references must always bind to a valid object

Use references when you want to modify an argument without the syntax overhead of *. Use pointers when you need nullable types or when you will reassign which object you point to.

Dangling Pointers and nullptr

A dangling pointer points to memory that has been freed:

int* dangling() {
    int x = 5;
    return &x;  // DANGER: x goes out of scope; pointer dangles
}

int* allocate() {
    int* p = new int(42);  // heap allocation
    return p;               // safe — heap memory outlives the function
}

int main() {
    delete allocate();  // must free heap memory

    // Use nullptr to mark "no object"
    int* p = nullptr;
    if (p != nullptr) {
        // safe to dereference
    }
}

The rule: never return the address of a local stack variable. Only return pointers to heap-allocated memory or use references.

new and delete

// Single object
int* p = new int(42);
delete p;          // delete the single object
p = nullptr;       // always null after delete

// Arrays
int* arr = new int[100];
delete[] arr;      // MUST use delete[] for arrays
arr = nullptr;

// Smart pointer — preferred over raw new/delete
#include <memory>
auto p = std::make_unique<int>(42);  // auto-deletes on scope exit
auto arr = std::make_unique<int[]>(100);

Never mix new/delete with new[]/delete[]. Never call delete twice on the same pointer.

Classes and RAII

Basic Class Structure

class Counter {
private:
    int count_ = 0;  // private by default in class
public:
    void increment() { ++count_; }
    int value() const { return count_; }  // const method — doesn't modify state
};

int main() {
    Counter c;
    c.increment();
    c.increment();
    std::cout << c.value() << "\n";  // 2
}

The const qualifier on value() promises the method won't modify the object. The compiler enforces this.

Constructors and Destructors

class DatabaseConnection {
private:
    std::string host_;
    int port_;
    void* handle_;  // raw handle to native resource

public:
    // Constructor — called when object is created
    DatabaseConnection(const std::string& host, int port)
        : host_(host), port_(port), handle_(nullptr) {
        // Acquire resource
        handle_ = connect(host_.c_str(), port_);
        std::cout << "connected to " << host_ << ":" << port_ << "\n";
    }

    // Destructor — called when object goes out of scope
    ~DatabaseConnection() {
        if (handle_) {
            disconnect(handle_);
            std::cout << "disconnected from " << host_ << "\n";
        }
    }

    // Move constructor — transfers ownership
    DatabaseConnection(DatabaseConnection&& other) noexcept
        : host_(std::move(other.host_)),
          port_(other.port_),
          handle_(other.handle_) {
        other.handle_ = nullptr;  // prevent destructor from freeing
    }

    // Copy constructor — deleted (no copying connections)
    DatabaseConnection(const DatabaseConnection&) = delete;

    void query(const std::string& sql) {
        if (!handle_) throw std::runtime_error("not connected");
        run_query(handle_, sql);
    }
};

void use_connection() {
    DatabaseConnection db("localhost", 5432);
    db.query("SELECT * FROM users");
}  // db destructor called automatically — connection closed

RAII: Resource Acquisition Is Initialization

RAII is C++'s most powerful pattern. The key insight: wrap every resource in an object whose destructor frees it. When the object goes out of scope, the destructor runs — guaranteed, even through exceptions.

// Before RAII — verbose, error-prone
void old_style() {
    FILE* f = fopen("data.txt", "r");
    if (!f) return;
    char buf[256];
    while (fgets(buf, sizeof(buf), f)) {
        // process line
    }
    fclose(f);  // easy to forget
}

// After RAII — automatic cleanup
void modern_style() {
    std::ifstream f("data.txt");  // destructor closes file
    std::string line;
    while (std::getline(f, line)) {
        // process line
    }
}  // f's destructor runs here — file is closed, even on exception

// C++ equivalent of "defer" — use a lambda scope guard
void with_scope_guard() {
    auto guard = std::experimental::scope_exit([&] {
        std::cout << "cleanup runs no matter what\n";
    });
    // ... risky code ...
    guard.release();  // cancel cleanup if needed
}

This is how std::lock_guard, std::unique_lock, std::fstream, std::vector, and every standard container work. Acquire in constructor, release in destructor.

Smart Pointers

Smart pointers wrap raw pointers with RAII semantics:

#include <memory>

// std::unique_ptr — exclusive ownership, zero overhead
auto p1 = std::make_unique<int>(42);
auto p2 = std::make_unique<int[]>(100);  // array variant
// unique_ptr<int> p3 = p1;  // ERROR: copy deleted
// std::unique_ptr<int> p4 = std::move(p1);  // transfer ownership
// p1 is now nullptr; p4 owns the int

// std::shared_ptr — shared ownership, reference counted
auto s1 = std::make_shared<int>(42);
auto s2 = s1;  // reference count goes to 2
// When s1 and s2 go out of scope, ref count hits 0, int is deleted

// std::weak_ptr — non-owning reference to shared_ptr
auto sp = std::make_shared<int>(100);
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) {  // checks if still alive
    std::cout << *locked << "\n";
}

void process(std::unique_ptr<Config> cfg) {  // takes ownership
    cfg->run();
}
process(std::make_unique<Config>());  // create and move in

Rule: never use raw new/delete in modern C++. Use std::make_unique and std::make_shared.

Inheritance

class Animal {
protected:
    std::string name_;
public:
    explicit Animal(const std::string& name) : name_(name) {}
    virtual ~Animal() = default;  // virtual destructor for polymorphic deletion
    virtual void speak() const { std::cout << "...\n"; }
    const std::string& name() const { return name_; }
};

class Dog : public Animal {
public:
    explicit Dog(const std::string& name) : Animal(name) {}
    void speak() const override { std::cout << name_ << " says woof\n"; }
};

class Cat : public Animal {
public:
    explicit Cat(const std::string& name) : Animal(name) {}
    void speak() const override { std::cout << name_ << " says meow\n"; }
};

void make_speak(const Animal& animal) {
    animal.speak();  // virtual dispatch calls correct subclass method
}

int main() {
    std::vector<std::unique_ptr<Animal>> zoo;
    zoo.push_back(std::make_unique<Dog>("Buddy"));
    zoo.push_back(std::make_unique<Cat>("Whiskers"));
    for (const auto& a : zoo) {
        a->speak();  // virtual call: woof, then meow
    }
}

The override keyword ensures the method actually overrides a base-class virtual method — if you misspell the name or change the signature, the compiler catches it. Always use override when you mean it.

Templates

Templates let you write code that works for any type. The compiler generates concrete code at compile time (monomorphization).

Function Templates

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    std::cout << max(1, 2) << "\n";       // int — compiler generates max<int>
    std::cout << max(3.14, 2.71) << "\n"; // double — compiler generates max<double>
    std::cout << max("apple", "banana") << "\n"; // const char* — pointer comparison
}

typename and class are interchangeable in template parameters. Use typename for clarity.

Class Templates

template <typename T, size_t N>
class Stack {
    std::array<T, N> data_;
    size_t top_ = 0;
public:
    bool empty() const { return top_ == 0; }
    bool full() const { return top_ == N; }
    void push(T value) {
        if (full()) throw std::overflow_error("stack full");
        data_[top_++] = value;
    }
    T pop() {
        if (empty()) throw std::underflow_error("stack empty");
        return data_[--top_];
    }
};

int main() {
    Stack<int, 128> s;
    s.push(42);
    std::cout << s.pop() << "\n";
}

Template Constraints (C++20 Concepts)

#include <concepts>

// Only works for types with a < operator
template <std::totally_ordered T>
T maximum(T a, T b) { return a > b ? a : b; }

// Works only for integral types
template <std::integral T>
T factorial(T n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// auto return type deduction + concepts
template <std::range R>
auto sum(const R& r) {
    using T = std::ranges::range_value_t<R>;
    T result = T();
    for (const auto& x : r) result += x;
    return result;
}

Concepts make template errors much clearer — instead of pages of incomprehensible error messages, you get "T does not satisfy std::integral."

Variadic Templates

// Variadic print — accepts any number of arguments
void print() { std::cout << "\n"; }

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...);  // recursive unpack
}

int main() {
    print("values:", 1, 2.5, "hello", true);  // values: 1 2.5 hello 1
}

sizeof...(Args) gives the number of variadic arguments. This is the foundation of std::printf alternatives and type-safe formatting.

Template Specialization

template <typename T>
struct Serializer {
    static std::string serialize(const T& v) {
        return std::to_string(v);
    }
};

// Specialization for std::string
template <>
struct Serializer<std::string> {
    static std::string serialize(const std::string& v) {
        return "\"" + v + "\"";
    }
};

// Partial specialization for pointers
template <typename T>
struct Serializer<T*> {
    static std::string serialize(T* p) {
        if (!p) return "null";
        return "ptr(" + Serializer<T>::serialize(*p) + ")";
    }
};

int main() {
    std::cout << Serializer<int>::serialize(42) << "\n";
    std::cout << Serializer<std::string>::serialize("hello") << "\n";
    int x = 7;
    std::cout << Serializer<int*>::serialize(&x) << "\n";
}

STL Basics

The Standard Template Library provides containers, algorithms, and iterators. It is header-only — no linking required.

Containers Overview

#include <vector>      // dynamic array — fast random access, fast append
#include <list>        // doubly-linked list — fast insert/delete anywhere
#include <deque>       // double-ended queue — fast insert/delete at both ends
#include <set>         // sorted tree — fast search, unique elements
#include <map>         // sorted key-value — fast search by key
#include <unordered_map>  // hash table — O(1) average lookup
#include <unordered_set>  // hash set — O(1) average lookup
#include <stack>       // LIFO stack adapter
#include <queue>       // FIFO queue adapter
#include <array>       // fixed-size array with .size()

vector — the Default Container

std::vector is the right default choice. Only use alternatives when you have a specific reason:

std::vector<int> v = {3, 1, 4, 1, 5};

// Random access — O(1)
std::cout << v[2] << "\n";

// Iterators — like pointers with extra methods
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}

// Range-based for — syntactic sugar over iterators
for (int x : v) {
    std::cout << x << " ";
}

// C++20 ranges — cleaner functional style
for (int x : std::views::all(v) | std::views::filter([](int n){ return n % 2 == 0; })) {
    std::cout << x << " ";
}

// Insert at end — amortized O(1)
v.push_back(9);
v.emplace_back(2);  // constructs in-place — no copy/move

// Sort — O(n log n)
std::sort(v.begin(), v.end());

// Erase by iterator — O(n) for vector
v.erase(v.begin() + 2);

// Reserve capacity to avoid reallocations
v.reserve(1000);  // capacity >= 1000, size unchanged

map and unordered_map

// std::map — sorted by key, O(log n) lookup
std::map<std::string, int> scores;
scores["alice"] = 95;
scores["bob"] = 87;
for (const auto& [name, score] : scores) {  // C++17 structured bindings
    std::cout << name << ": " << score << "\n";
}
// Output: alice: 95, bob: 87 (sorted alphabetically)

// std::unordered_map — hash table, O(1) average lookup
std::unordered_map<std::string, int> fast_scores;
fast_scores.reserve(1000);  // pre-allocate for performance
fast_scores["alice"] = 95;
fast_scores["bob"] = 87;
// Output order: undefined (hash order)

// Safe lookup with .find()
auto it = scores.find("charlie");
if (it != scores.end()) {
    std::cout << "found: " << it->second << "\n";
} else {
    std::cout << "not found\n";
}

// .at() throws if key missing; [] inserts default if missing
scores.at("alice");  // throws if not present
scores["missing"];   // inserts with value-initialized int (0)

// Insert or update
auto [iterator, inserted] = scores.insert_or_assign("alice", 99);
// iterator points to element, inserted = false (alice already existed)

Algorithms

#include <algorithm>
#include <numeric>

std::vector<int> v = {5, 2, 8, 1, 9, 3};

std::sort(v.begin(), v.end());
std::reverse(v.begin(), v.end());
auto it = std::find(v.begin(), v.end(), 8);     // find element
auto count = std::count_if(v.begin(), v.end(),
    [](int n) { return n % 2 == 0; });          // count evens

std::transform(v.begin(), v.end(), v.begin(),
    [](int n) { return n * 2; });               // double each element

int sum = std::accumulate(v.begin(), v.end(), 0);  // sum = 28
bool any_even = std::any_of(v.begin(), v.end(),
    [](int n) { return n % 2 == 0; });
bool all_pos = std::ranges::all_of(v, [](int n) { return n > 0; });

// Copy to another container
std::vector<int> evens;
std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
    [](int n) { return n % 2 == 0; });

Lambda Expressions

Lambdas create anonymous function objects:

auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << "\n";  // 7

// Capture variables from surrounding scope
int multiplier = 3;
auto scale = [multiplier](int x) { return x * multiplier; };
std::cout << scale(5) << "\n";  // 15

// Capture by value [=] or reference [&]
std::vector<std::function<int(int)>> ops;
ops.push_back([](int x) { return x + 1; });   // value capture
ops.push_back([](int x) { return x * 2; });

// C++14 generic lambda
auto make_op = [](auto x) { return [x](auto y) { return x + y; }; };

Build Systems: CMake

CMake generates platform-native build files (Makefiles, Ninja, IDE projects) from CMakeLists.txt.

Minimal Project

my_project/
├── CMakeLists.txt
└── src/
    ├── main.cpp
    └── math.cpp
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# Require C++20
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)  # use -std=c++20, not -std=gnu++20

# Enable compiler warnings as errors
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(my_project PRIVATE
        -Wall -Wextra -Wpedantic
    )
elseif(MSVC)
    target_compile_options(my_project PRIVATE
        /W4 /WX
    )
endif()

# Source files
add_executable(my_project
    src/main.cpp
    src/math.cpp
)

# Link libraries
target_link_libraries(my_project PRIVATE
    Threads::Threads   # standard threading library
)
# Configure and build (out-of-source build)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

# Run
./build/my_project

Finding Dependencies

# Find a package
find_package(Threads REQUIRED)

# Add an external dependency fetched at configure time
include(FetchContent)
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)

# Now use GoogleTest
enable_testing()
add_executable(my_test tests/my_test.cpp)
target_link_libraries(my_test PRIVATE GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(my_test)

Targets and Modern CMake

Modern CMake uses targets to model dependencies. Libraries are only exposed to targets that explicitly link them:

# Define a library
add_library(math math.cpp)

# Its include directories are private — users must explicitly request them
target_include_directories(math PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}
)

# Depend on it
add_executable(app main.cpp)
target_link_libraries(app PRIVATE math)  # PRIVATE = app only, math doesn't see it

PRIVATE = only this target; PUBLIC = this target and its consumers; INTERFACE = consumers only (header-only libs).

Variables and Cache

set(MY_FLAG "value" CACHE STRING "Description")  # cache variable — persists across runs
option(BUILD_TESTS "Build tests" ON)             # boolean option

# Generator expressions — evaluated at build time
target_compile_definitions(app PRIVATE
    $<$<CONFIG:Release>:NDEBUG>  # NDEBUG only in Release
    $<$<BOOL:USE_SSE>:ENABLE_SSE>
)

Guided Checkpoint

Configure a minimal CMake project, build it, and run the resulting binary:

mkdir -p /tmp/cpp-checkpoint && cd /tmp/cpp-checkpoint
cat > CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.16)
project(Checkpoint LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(checkpoint src/main.cpp)
EOF
mkdir -p src
cat > src/main.cpp <<'EOF'
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v{5, 2, 8, 1, 9, 3};
    std::sort(v.begin(), v.end());
    for (int x : v) std::cout << x << " ";
    std::cout << "\n";
    return 0;
}
EOF

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/checkpoint
# Expected output: 1 2 3 5 8 9

3. Anti-Patterns & Common Pitfalls

These failure modes appear repeatedly in C++ codebases. Each is detectable, preventable, and rooted in C++'s manual memory management or template complexity.

Anti-Pattern: Manual new and delete

In modern C++ (2011+), new and delete should be reserved for low-level memory management. Use smart pointers instead — they apply RAII automatically:

// Bad: manual delete is easy to forget, especially through exceptions
void process() {
    int* data = new int[100];
    // ... if any line throws, the array leaks
    delete[] data;
}

// Good: smart pointer deletes on scope exit, even through exceptions
void process() {
    auto data = std::make_unique<int[]>(100);
    // ... no manual delete needed
}

Detection: search for new and delete in non-implementation files. Modern codebases should have very few matches.

Fix: replace T* p = new T(...) with auto p = std::make_unique<T>(...). Replace T* arr = new T[n] with auto arr = std::make_unique<T[]>(n). Use std::shared_ptr only when shared ownership is genuinely required.

Anti-Pattern: Mixing new[] with delete (or vice versa)

new T[] allocates an array and requires delete[]. Using the wrong delete form is undefined behavior — it usually crashes or corrupts the heap:

// Bad: UB — array allocated with new[] but freed with delete
int* arr = new int[100];
delete arr;  // undefined behavior

// Bad: UB — single object freed with delete[]
int* p = new int(42);
delete[] p;  // undefined behavior

Fix: never use raw new/delete. Smart pointers handle this correctly: std::make_unique<T[]>(n) allocates an array and frees it with delete[] automatically.

Anti-Pattern: Dangling Pointers from Returning Local Addresses

Returning the address of a local variable is a classic C/C++ bug. The variable is destroyed when the function returns, and the pointer dangles:

// Bad: returns address of stack-local variable
int* make_default() {
    int x = 42;
    return &x;  // x is destroyed when function returns — pointer dangles
}

// Good: allocate on the heap (but prefer smart pointers)
std::unique_ptr<int> make_default() {
    return std::make_unique<int>(42);
}

// Better: return by value
int make_default() {
    return 42;
}

Fix: prefer returning by value when possible. Use smart pointers when the caller must take ownership of heap memory. Never return the address of a local variable.

Anti-Pattern: Forgetting virtual Destructors in Base Classes

Deleting a derived class through a base pointer without a virtual destructor is undefined behavior — the derived destructor never runs, and resources leak:

// Bad: no virtual destructor
class Base {
public:
    ~Base() = default;  // not virtual
};
class Derived : public Base {
    std::unique_ptr<int[]> data_;
};
Base* p = new Derived;
delete p;  // UB: only Base::~Base runs, Derived's data_ never freed

// Good: virtual destructor in polymorphic base class
class Base {
public:
    virtual ~Base() = default;
};

Fix: any class intended to be used polymorphically (i.e., deleted through a base pointer) must have a virtual destructor. Use override on derived destructors for safety.


4. Independent Challenge

Design a small resource-pool class that owns a fixed number of reusable resources, without step-by-step guidance.

Without step-by-step guidance, produce:

  1. A ResourcePool class template that owns a fixed set of reusable resources (use a std::vector<std::unique_ptr<Resource>> internally).
  2. An acquire() method that returns an RAII handle — a wrapper that automatically returns the resource when the handle is destroyed.
  3. A release() method (or a callback the handle calls on destruction) that puts the resource back in the pool.
  4. A size() method that reports how many resources are currently available.
  5. A wait() method (or alternative mechanism) that blocks until a resource is available if the pool is exhausted.

Constraints:

Deliverable: a single header (and a test file) that demonstrates the pool with a small Resource type (e.g., a network connection or a thread-pool worker). Verify with a stress test that spawns N threads, each acquiring and releasing M resources. No solution is provided.


5. Consolidation & Key Invariants


6. Next Steps

Next Article: rust-basics — for a side-by-side comparison of memory safety models, or runtime-internals — to understand how a JIT-compiled runtime works under the hood.


Content depth matches kb/containers/docker-concepts.md — production-level patterns, real syntax, and practical examples.

Change Log

Date Change
2026-09-15 Migrated to canonical 6-section template (Architectural Overview, Deep Dive with Guided Checkpoint, Anti-Patterns, Independent Challenge, Consolidation, Next Steps). Preserved all prior content, prerequisites, and tags.
2026-08-29 Full hydration of C++ Basics lesson
2026-08-29 Added sections: Why C++, Memory, Classes and RAII, Templates, STL, Build Systems
2026-09-15 Added Next Steps links, preserved all original content and prerequisites

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse