The C++ Programming Language
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.
- › 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
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
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
- › 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
| Container | Access | Insert(end) | Insert(mid) | Search |
|---|---|---|---|---|
vector<T> | O(1) random | O(1) amortized | O(n) | O(n) / O(log n) sorted |
deque<T> | O(1) random | O(1) both ends | O(n) | O(n) |
list<T> | O(n) | O(1) anywhere | O(1) | O(n) |
unordered_map | O(1) avg | O(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_queue | O(1) top | O(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, " "));