The C++ Programming Language

The C++ Programming Language, 4th Edition · Bjarne Stroustrup ·1346 pages

Definitive C++11 reference by Stroustrup: type system, uniform initialization, RAII/ownership, class design (Rule of Five), operator overloading, virtual dispatch, templates, generic programming, variadic templates, metaprogramming, STL containers/algorithms/iterators, lambdas, concurrency (threads/futures/atomics), and C++20 ranges.

Capabilities (7)
  • Apply RAII pattern to manage resources safely with constructors and destructors
  • Design class hierarchies with virtual dispatch, override, and virtual destructors
  • Write function and class templates with type deduction and specialization
  • Select appropriate STL containers based on access/insert/search complexity
  • Compose STL algorithms with lambdas using erase-remove and transform patterns
  • Use std::async, std::future, std::atomic, and mutex for safe concurrent code
  • Apply template metaprogramming with type_traits and constexpr for compile-time logic
How to use

Install this skill and Claude can apply RAII ownership, Rule of Five class design, template metaprogramming with type_traits and constexpr, and the full STL algorithm suite to write idiomatic, correct C++11 code from first principles

Why it matters

As Stroustrup's own definitive reference, this skill grounds every C++ recommendation in the language's design intent — covering the full language from its type system through concurrency — so Claude produces authoritative, standard-grounded guidance

Example use cases
  • Reviewing a class that manages a raw resource and adding the correct Rule of Five members — copy constructor, copy assignment, move constructor, move assignment, and destructor
  • Replacing a hand-written loop over a vector with the correct STL algorithm composition using transform, remove_if, and accumulate with lambdas
  • Designing a variadic template function that accumulates values of mixed types using recursive pack expansion and constexpr

The C++ Programming Language Skill

Core Language Philosophy

  • Zero-overhead abstraction: abstractions cost no more than hand-written code
  • Resource management via types: RAII — constructor acquires, destructor releases
  • Type safety: the type system prevents common classes of bugs at compile time
  • Value semantics: C++ objects are values by default; references/pointers are explicit
  • Initialization matters: always initialize; use {} for uniform initialization

Types and Declarations

Fundamental Types

// Integer types (use explicit width types for portability)
#include <cstdint>
int8_t, int16_t, int32_t, int64_t
uint8_t, uint16_t, uint32_t, uint64_t
size_t         // result of sizeof, index into arrays

// Floating point
float          // 32-bit
double         // 64-bit (prefer this)
long double    // 80/128-bit (implementation-defined)

// Type aliases
using Byte = unsigned char;
using Index = std::ptrdiff_t;

Uniform Initialization (C++11)

int x{7};                    // narrowing conversion prevented
double d{2.5};
std::string s{"hello"};
std::vector<int> v{1, 2, 3};

// Prefer {} over () for initialization to avoid most vexing parse
// and to prevent implicit narrowing conversions
struct Point { int x, y; };
Point p{3, 4};               // aggregate initialization

auto and decltype

auto x = 42;                 // int
auto& r = x;                 // int& (reference to x)
auto* p = &x;                // int*

// decltype: exact type of expression
decltype(x) y = x;           // same type as x
decltype(auto) f() { return x; }  // preserve ref/value

// Range-for with auto
for (auto& element : container) { /* ... */ }

Pointers, References, and Ownership

Raw Pointers vs Smart Pointers

// Avoid owning raw pointers — use smart pointers instead

// unique_ptr: exclusive ownership
#include <memory>
auto p = std::make_unique<Widget>(args);  // preferred
std::unique_ptr<Widget> p{new Widget{}};  // alternate

p->method();        // use as pointer
*p;                 // dereference
p.reset();          // delete and set to nullptr
auto raw = p.get(); // non-owning raw pointer (don't delete)

// shared_ptr: shared ownership
auto sp = std::make_shared<Widget>(args);
auto sp2 = sp;      // reference count = 2
// destructs when last shared_ptr goes out of scope

// weak_ptr: observe without owning (breaks cycles)
std::weak_ptr<Widget> wp = sp;
if (auto locked = wp.lock()) {  // check if still alive
    locked->method();
}

References

int x = 42;
int& r = x;      // reference — must be initialized, cannot be rebound
r = 99;          // modifies x

const int& cr = x;   // const ref: read-only, can bind to rvalue
const int& cr2 = 42; // extends lifetime of temporary

// Rvalue references (C++11)
int&& rr = 42;        // rvalue reference
void f(int&& x);      // accepts only rvalues/moved values

Classes and Abstraction

Class Design Rules (Stroustrup)

class Widget {
public:
    // Rule of zero: if no resources, don't declare special members
    // Rule of five: if you declare one of destructor/copy/move, declare all five

    Widget(int x, std::string name)    // constructor
        : x_{x}, name_{std::move(name)} {}  // use member init list

    ~Widget() {}                        // destructor (RAII)
    Widget(const Widget&) = default;    // copy constructor
    Widget& operator=(const Widget&) = default;  // copy assignment
    Widget(Widget&&) noexcept = default;         // move constructor
    Widget& operator=(Widget&&) noexcept = default;  // move assignment

    // Mark mutating vs non-mutating
    int value() const { return x_; }   // const: doesn't modify this
    void setValue(int x) { x_ = x; }   // non-const: modifies this

private:
    int x_;
    std::string name_;
};

RAII Pattern

// Resource Acquisition Is Initialization
// Acquire in constructor, release in destructor
class MutexLock {
public:
    explicit MutexLock(std::mutex& m) : mutex_(m) { m.lock(); }
    ~MutexLock() { mutex_.unlock(); }

    // Delete copy to prevent double-unlock
    MutexLock(const MutexLock&) = delete;
    MutexLock& operator=(const MutexLock&) = delete;

private:
    std::mutex& mutex_;
};

// Usage: lock automatically released when scope exits
{
    MutexLock lock{my_mutex};
    // ... critical section
}  // ← destructor called, mutex unlocked

// Prefer stdlib: std::lock_guard<std::mutex> or std::scoped_lock

Operator Overloading

class Complex {
    double re, im;
public:
    Complex(double r, double i) : re{r}, im{i} {}

    // Arithmetic (non-member preferred for symmetry)
    friend Complex operator+(Complex a, Complex b) {
        return {a.re + b.re, a.im + b.im};
    }
    friend Complex operator-(Complex a, Complex b) {
        return {a.re - b.re, a.im - b.im};
    }
    // Compound assignment (member)
    Complex& operator+=(Complex z) { re += z.re; im += z.im; return *this; }
    // Comparison (C++20: use =default for auto-generated spaceship operator)
    bool operator==(const Complex& z) const = default;
    // Stream output
    friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
        return os << '(' << c.re << ',' << c.im << ')';
    }
};

Inheritance and Polymorphism

Hierarchy Design

class Shape {
public:
    virtual double area() const = 0;    // pure virtual = abstract
    virtual double perimeter() const = 0;
    virtual void draw() const = 0;
    virtual ~Shape() {}                  // virtual destructor (critical!)
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_{r} {}
    double area() const override { return M_PI * radius_ * radius_; }
    double perimeter() const override { return 2 * M_PI * radius_; }
    void draw() const override { /* draw circle */ }
};

// Use through base class pointer
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
for (const auto& s : shapes)
    s->draw();           // virtual dispatch

Runtime Type Information

// dynamic_cast: safe downcast (returns nullptr if wrong type)
Shape* s = get_some_shape();
if (Circle* c = dynamic_cast<Circle*>(s)) {
    c->radius();     // safe: s is actually a Circle
}

// typeid: get type info at runtime
if (typeid(*s) == typeid(Circle)) { /* ... */ }

// Generally: prefer virtual functions over RTTI

Templates and Generic Programming

Function Templates

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

max(3, 4);               // T = int (deduced)
max(3.14, 2.71);         // T = double
max<int>(3, 4);          // explicit

// Concept constraints (C++20)
template<std::totally_ordered T>
T max(T a, T b) { return a > b ? a : b; }

Class Templates

template<typename T, int N>
class Array {
    T data_[N];
public:
    constexpr int size() const { return N; }
    T& operator[](int i) { return data_[i]; }
    const T& operator[](int i) const { return data_[i]; }
    T* begin() { return data_; }
    T* end() { return data_ + N; }
};

Array<int, 5> arr;    // stack-allocated array of 5 ints

Variadic Templates

// Recursive variadic template
template<typename T>
void print(const T& t) { std::cout << t << '\n'; }

template<typename T, typename... Rest>
void print(const T& t, const Rest&... rest) {
    std::cout << t << ' ';
    print(rest...);    // recursive call with one fewer argument
}

print(1, 2.5, "hello");  // prints: 1 2.5 hello

Template Metaprogramming (Compile-Time Computation)

// Compile-time factorial
template<int N>
struct Factorial {
    static constexpr int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
    static constexpr int value = 1;
};
constexpr int f5 = Factorial<5>::value;  // 120, computed at compile time

// Modern: constexpr functions (simpler)
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n-1);
}

// Type traits (from <type_traits>)
static_assert(std::is_integral_v<int>);
static_assert(!std::is_pointer_v<int>);
using T = std::remove_const_t<const int>;  // T = int
using U = std::conditional_t<true, int, double>;  // U = int

STL Containers

Container Selection Guide

ContainerAccessInsert(end)Insert(mid)Search
vector<T>O(1) randomO(1) amortizedO(n)O(n) / O(log n) sorted
deque<T>O(1) randomO(1) both endsO(n)O(n)
list<T>O(n)O(1) anywhereO(1)O(n)
unordered_mapO(1) avgO(1) avg-O(1) avg
map<K,V>O(log n)O(log n)-O(log n)
set<T>O(log n)O(log n)-O(log n)
priority_queueO(1) topO(log n)--
#include <vector>
#include <unordered_map>
#include <map>
#include <set>

// vector: contiguous, prefer for most use cases
std::vector<int> v{1, 2, 3};
v.push_back(4);
v.emplace_back(5);          // construct in place (avoids copy)
v.reserve(100);             // pre-allocate to avoid reallocations

// unordered_map: O(1) average lookup
std::unordered_map<std::string, int> freq;
freq["hello"]++;
if (freq.count("hello")) { /* exists */ }
auto [val, inserted] = freq.emplace("world", 1);  // C++17 structured binding

// map: sorted, O(log n) operations
std::map<std::string, int> sorted_map;
for (auto& [key, val] : sorted_map) { /* ordered iteration */ }

STL Algorithms

#include <algorithm>
#include <numeric>

// Search
auto it = std::find(v.begin(), v.end(), 42);
auto it = std::find_if(v.begin(), v.end(), [](int x){ return x > 10; });
bool has = std::binary_search(sorted.begin(), sorted.end(), 42);  // requires sorted

// Transform
std::transform(v.begin(), v.end(), out.begin(), [](int x){ return x * 2; });
std::for_each(v.begin(), v.end(), [](int& x){ x *= 2; });  // in-place

// Sort and partition
std::sort(v.begin(), v.end());
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });  // descending
std::stable_sort(v.begin(), v.end());   // preserves order of equal elements

// Accumulate / reduce
int sum = std::accumulate(v.begin(), v.end(), 0);
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>{});

// Erase-remove idiom (remove elements in-place)
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x < 0; }), v.end());
// C++20: std::erase_if(v, predicate);

Lambdas

// Capture modes
auto f1 = [](){};               // capture nothing
auto f2 = [x](){};              // capture x by value
auto f3 = [&x](){};             // capture x by reference
auto f4 = [=](){};              // capture all by value (avoid in classes)
auto f5 = [&](){};              // capture all by reference (avoid for async)
auto f6 = [=, &x](){};          // all by value, x by reference

// Generic lambda (C++14)
auto compare = [](auto a, auto b){ return a < b; };

// Mutable lambda (modify captured values)
int count = 0;
auto counter = [count]() mutable { return ++count; };

// Lambda as function argument
std::sort(people.begin(), people.end(),
    [](const Person& a, const Person& b){ return a.age < b.age; });

Concurrency (C++11)

#include <thread>
#include <mutex>
#include <future>
#include <atomic>

// Threads
std::thread t{[]{ do_work(); }};
t.join();   // wait for completion; MUST call join() or detach()

// Mutex protection
std::mutex mtx;
std::lock_guard<std::mutex> lock{mtx};  // RAII lock
// or: std::unique_lock<std::mutex> for conditional_variable compatibility

// Atomic: lock-free for simple types
std::atomic<int> counter{0};
counter++;                   // atomic increment, no mutex needed
int val = counter.load();

// Futures and promises (task-based concurrency)
auto fut = std::async(std::launch::async, []{ return compute(); });
// ... do other work ...
int result = fut.get();      // blocks until compute() finishes, propagates exceptions

// Promise: manually provide future value
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t{[&p]{ p.set_value(42); }};
int val = f.get();           // 42
t.join();

Exception Handling

// Throw by value, catch by const reference
try {
    throw std::runtime_error{"Something went wrong"};
} catch (const std::runtime_error& e) {
    std::cerr << e.what() << '\n';
} catch (const std::exception& e) {
    // catch all standard exceptions
} catch (...) {
    // catch all (but can't inspect it)
    throw;   // re-throw
}

// noexcept: promise not to throw (enables compiler optimizations)
void f() noexcept { /* ... */ }  // std::terminate if exception escapes

// RAII makes exception safety easier:
// Resources managed by objects are cleaned up during stack unwinding
// Avoid raw new/delete; use make_unique/make_shared

Ranges and Pipelines (C++20)

#include <ranges>

// Filter + transform pipeline
auto result = v
    | std::views::filter([](int x){ return x > 0; })
    | std::views::transform([](int x){ return x * 2; });

// Lazy evaluation — only computes when iterated
for (int x : result) { std::cout << x << ' '; }

// Range algorithms (work on whole containers)
std::ranges::sort(v);
std::ranges::find(v, 42);
std::ranges::copy(v, std::ostream_iterator<int>(std::cout, " "));