Add modern-cpp plugin for C++20/23/26 best practices

Modern C++ skill guiding Claude toward modern idioms with a security
emphasis. Mirrors modern-python in spirit but focuses on language
standards rather than toolchain.

Features tiered by practical usability:
- Tier 1 (Use Today): C++20/23 features with solid compiler support
- Tier 2 (Deploy Now): Compiler hardening, sanitizers, hardened libc++
- Tier 3 (Plan For): C++26 reflection
- Tier 4 (Watch): Contracts, std::execution

Includes SKILL.md entry point + 6 reference docs:
- anti-patterns.md (30+ legacy-to-modern swaps)
- cpp20-features.md (concepts, ranges, span, format, coroutines)
- cpp23-features.md (expected, print, deducing this, flat_map)
- cpp26-features.md (reflection, contracts, memory safety)
- compiler-hardening.md (flags, sanitizers, hardened libc++)
- safe-idioms.md (security patterns by vulnerability class)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
wizardengineer
2026-04-01 14:01:32 -04:00
parent d7f76b532d
commit 4c9d5989a4
13 changed files with 1647 additions and 0 deletions
+11
View File
@@ -383,6 +383,17 @@
"url": "https://github.com/trailofbits"
},
"source": "./plugins/dimensional-analysis"
},
{
"name": "modern-cpp",
"version": "1.0.0",
"description": "Modern C++ best practices (C++20/23/26). Use when writing C++ code, creating new C++ projects, or modernizing legacy C++ patterns.",
"author": {
"name": "Julius Alexandre",
"email": "opensource@trailofbits.com",
"url": "https://github.com/trailofbits"
},
"source": "./plugins/modern-cpp"
}
]
}
+1
View File
@@ -0,0 +1 @@
../../plugins/modern-cpp/skills/modern-cpp
+1
View File
@@ -22,6 +22,7 @@
/plugins/git-cleanup/ @hbrodin @dguido
/plugins/insecure-defaults/ @dariushoule @dguido
/plugins/let-fate-decide/ @tob-scott-a @dguido
/plugins/modern-cpp/ @wizardengineer @dguido
/plugins/modern-python/ @Ninja3047 @dguido
/plugins/mutation-testing/ @bohendo @dguido
/plugins/property-based-testing/ @hbrodin @dguido
+1
View File
@@ -106,6 +106,7 @@ cd /path/to/parent # e.g., if repo is at ~/projects/skills, be in ~/projects
| [gh-cli](plugins/gh-cli/) | Intercept GitHub URL fetches and redirect to the authenticated `gh` CLI |
| [git-cleanup](plugins/git-cleanup/) | Safely clean up git worktrees and local branches with gated confirmation workflow |
| [let-fate-decide](plugins/let-fate-decide/) | Draw Tarot cards using cryptographic randomness to add entropy to vague planning |
| [modern-cpp](plugins/modern-cpp/) | Modern C++ best practices (C++20/23/26) with compiler hardening and safe idioms |
| [modern-python](plugins/modern-python/) | Modern Python tooling and best practices with uv, ruff, and pytest |
| [seatbelt-sandboxer](plugins/seatbelt-sandboxer/) | Generate minimal macOS Seatbelt sandbox configurations |
| [second-opinion](plugins/second-opinion/) | Run code reviews using external LLM CLIs (OpenAI Codex, Google Gemini) on changes, diffs, or commits. Bundles Codex's built-in MCP server. |
@@ -0,0 +1,10 @@
{
"name": "modern-cpp",
"version": "1.0.0",
"description": "Modern C++ best practices (C++20/23/26). Use when writing C++ code, creating new C++ projects, or modernizing legacy C++ patterns.",
"author": {
"name": "Julius Alexandre",
"email": "opensource@trailofbits.com",
"url": "https://github.com/trailofbits"
}
}
+41
View File
@@ -0,0 +1,41 @@
# modern-cpp
Modern C++ best practices plugin for Claude Code, guiding AI-assisted development toward C++20/23/26 idioms with a security emphasis from Trail of Bits.
## When to Use
- Writing new C++ code (functions, classes, libraries)
- Modernizing legacy C++ patterns (pre-C++20)
- Working on security-critical C++ code
- Reviewing C++ code for modern idiom adoption
- Choosing between legacy and modern approaches to a problem
## What It Covers
### Language Features (Tiered by Usability)
| Tier | Standard | What |
|------|----------|------|
| Use Today | C++20 | Concepts, ranges, `std::span`, `std::format`, coroutines, `<=>` |
| Use Today | C++23 | `std::expected`, `std::print`, deducing `this`, `std::flat_map` |
| Deploy Now | Any | Compiler hardening flags, sanitizers, hardened libc++ |
| Plan For | C++26 | Reflection (eliminates serialization boilerplate and code generators) |
| Watch | C++26 | Contracts, `std::execution` (promising but needs compiler maturity) |
### Security
- Compiler hardening flags (GCC + Clang, per OpenSSF guidelines)
- Hardened libc++ modes (bounds-checking with ~0.3% overhead)
- Sanitizer setup (ASan, UBSan, TSan, MSan)
- Safe idioms organized by vulnerability class (memory, type, integer, concurrency)
### Anti-Patterns
30+ legacy-to-modern pattern replacements with rationale, covering memory management, type safety, error handling, concurrency, and metaprogramming.
## Installation
```
/plugin marketplace add trailofbits/skills
/plugin install modern-cpp
```
@@ -0,0 +1,187 @@
---
name: modern-cpp
description: Guides C++ code toward modern idioms (C++20/23/26). Use when writing new C++ code, modernizing legacy patterns, or working on security-critical C++. Replaces raw pointers with smart pointers, SFINAE with concepts, printf with std::print, error codes with std::expected.
---
# Modern C++
Guide for writing modern C++ using C++20, C++23, and C++26 idioms. Focuses on patterns that eliminate vulnerability classes and reduce boilerplate, with a security emphasis from Trail of Bits.
## When to Use This Skill
- Writing new C++ functions, classes, or libraries
- Modernizing existing C++ code (pre-C++20 patterns)
- Choosing between legacy and modern approaches
- Working on security-critical or safety-sensitive C++
- Reviewing C++ code for modern idiom adoption
## When NOT to Use This Skill
- **User explicitly requires older standard**: Respect constraints (embedded, legacy ABI)
- **Pure C code**: This skill is C++-specific
- **Build system questions**: CMake, Meson, Bazel configuration is out of scope
- **Non-C++ projects**: Mixed codebases where C++ isn't primary
## Anti-Patterns to Avoid
| Avoid | Use Instead | Why |
|-------|-------------|-----|
| `new`/`delete` | `std::make_unique`, `std::make_shared` | Eliminates leaks, double-free |
| Raw owning pointers | `std::unique_ptr`, `std::shared_ptr` | RAII ownership semantics |
| C arrays (`int arr[N]`) | `std::array<int, N>` | Bounds-aware, value semantics |
| Pointer + length params | `std::span<T>` | Non-owning, bounds-checkable |
| `printf` / `sprintf` | `std::format`, `std::print` | Type-safe, no buffer overflow |
| C-style casts `(int)x` | `static_cast<int>(x)` | Explicit intent, auditable |
| `#define` constants | `constexpr` variables | Scoped, typed, debuggable |
| SFINAE / `enable_if` | Concepts + `requires` | Readable constraints and errors |
| Error codes + out params | `std::expected<T, E>` | Composable, type-safe errors |
| `union` | `std::variant` | Type-safe, no silent UB |
| Raw `mutex.lock()/unlock()` | `std::scoped_lock` | Exception-safe, no deadlocks |
| `std::thread` | `std::jthread` | Auto-join, stop token support |
| `assert()` macro | `contract_assert` (C++26) | Visible to tooling, configurable |
| Manual CRTP | Deducing `this` (C++23) | Simpler, no template boilerplate |
| Macro code generation | Reflection (C++26) | Zero-overhead, composable |
See [anti-patterns.md](./references/anti-patterns.md) for the full table (30+ patterns).
## Decision Tree
```
What are you doing?
|
+-- Writing new C++ code?
| +-- Use modern idioms by default (C++20/23)
| +-- Choose the newest standard your compiler supports
| +-- See Feature Tiers below
|
+-- Modernizing existing code?
| +-- Start with Tier 1 (C++20/23) replacements
| +-- Prioritize by security impact (memory > types > style)
| +-- See anti-patterns.md for the migration table
|
+-- Security-critical code?
| +-- Enable compiler hardening flags (see below)
| +-- Enable hardened libc++ mode
| +-- Run sanitizers in CI
| +-- See safe-idioms.md and compiler-hardening.md
|
+-- Using C++26 features?
+-- Reflection: YES, plan for it (GCC 16+)
+-- Contracts: cautiously, for new API boundaries
+-- std::execution: wait for ecosystem maturity
+-- See cpp26-features.md
```
## Feature Tiers
Features are ranked by **practical usability today**, not by standard version.
### Tier 1: Use Today (C++20/23, solid compiler support)
| Feature | Replaces | Standard |
|---------|----------|----------|
| Concepts + `requires` | SFINAE, `enable_if` | C++20 |
| Ranges + views | Raw iterator loops | C++20 |
| `std::span<T>` | Pointer + length | C++20 |
| `std::format` | `sprintf`, iostream chains | C++20 |
| Three-way comparison `<=>` | Manual comparison operators | C++20 |
| `std::jthread` | `std::thread` + manual join | C++20 |
| Designated initializers | Positional struct init | C++20 |
| `std::expected<T,E>` | Error codes, exceptions at boundaries | C++23 |
| `std::print` / `std::println` | `printf`, `std::cout <<` | C++23 |
| Deducing `this` | CRTP, const/non-const duplication | C++23 |
| `std::flat_map` | `std::map` for read-heavy use | C++23 |
| Monadic `std::optional` | Nested if-checks on optionals | C++23 |
See [cpp20-features.md](./references/cpp20-features.md) and [cpp23-features.md](./references/cpp23-features.md).
### Tier 2: Deploy Now (no standard bump needed)
These improve safety without changing your C++ standard version:
- **Compiler hardening flags** — `-D_FORTIFY_SOURCE=3`, `-fstack-protector-strong`, `-ftrivial-auto-var-init=zero`
- **Hardened libc++** — `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST` for ~0.3% overhead bounds-checking
- **Sanitizers in CI** — ASan + UBSan as minimum; TSan for concurrent code
- **Warning flags** — `-Wall -Wextra -Wpedantic -Werror`
See [compiler-hardening.md](./references/compiler-hardening.md).
### Tier 3: Plan For (C++26, worth restructuring around)
**Reflection** is the single most transformative C++26 feature. It eliminates:
- Serialization boilerplate (one generic function replaces per-struct `to_json`)
- Code generators (protobuf codegen, Qt MOC)
- Macro-based registration and enum-to-string hacks
GCC 16 (April 2026) has reflection merged. Plan new code to benefit from it.
### Tier 4: Watch (C++26, needs maturation)
- **Contracts** (`pre`/`post`/`contract_assert`) — Better than `assert()`, but no virtual function support and limited compiler support. Adopt cautiously for new API boundaries.
- **std::execution** (senders/receivers) — Powerful async framework, but steep learning curve, no scheduler ships with it, and poor documentation. Wait for ecosystem maturity.
See [cpp26-features.md](./references/cpp26-features.md).
## Compiler Hardening Quick Reference
### Essential Flags (GCC + Clang)
```
-Wall -Wextra -Wpedantic -Werror
-D_FORTIFY_SOURCE=3
-fstack-protector-strong
-fstack-clash-protection
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now
```
### Clang-Specific
```
-Wunsafe-buffer-usage
```
### Hardened libc++ (Clang/libc++ only)
```
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
```
Google deployed this across Chrome and their server fleet: ~0.3% overhead, 1000+ bugs found, 30% reduction in production segfaults.
See [compiler-hardening.md](./references/compiler-hardening.md) for the full guide.
## Rationalizations to Reject
| Rationalization | Why It's Wrong |
|----------------|----------------|
| "It compiles without warnings" | Warnings depend on which flags you enable. Add `-Wall -Wextra -Wpedantic`. |
| "ASan is too slow for production" | Use GWP-ASan for sampling-based production detection (~0% overhead). |
| "We only use safe containers" | Iterator invalidation and unchecked `optional` access are still exploitable. |
| "Smart pointers are slower" | `std::unique_ptr` has zero overhead vs raw pointers. Measure before claiming. |
| "Our code doesn't have memory bugs" | Google found 1000+ bugs when enabling hardened libc++. So did everyone else. |
| "C++26 features aren't available yet" | C++20/23 features are. Hardening flags work on any standard. Start there. |
| "Modern C++ is harder to read" | `std::expected` is more readable than checking error codes across 5 out-params. |
## Best Practices Checklist
- [ ] Use smart pointers for ownership, raw pointers only for non-owning observation
- [ ] Prefer `std::span` over pointer + length for function parameters
- [ ] Use `std::expected` for functions that can fail with typed errors
- [ ] Constrain templates with concepts, not SFINAE
- [ ] Enable compiler hardening flags and hardened libc++ in all builds
- [ ] Run ASan + UBSan in CI; add TSan for concurrent code
- [ ] Use `constexpr` / `consteval` where possible (UB-free by design)
- [ ] Mark functions `[[nodiscard]]` when ignoring the return value is likely a bug
- [ ] Prefer value semantics; use `std::variant` over `union`, `enum class` over `enum`
- [ ] Initialize all variables at declaration
## Read Next
- [anti-patterns.md](./references/anti-patterns.md) — Full legacy-to-modern migration table (30+ patterns)
- [cpp20-features.md](./references/cpp20-features.md) — Concepts, ranges, span, format, coroutines
- [cpp23-features.md](./references/cpp23-features.md) — expected, print, deducing this, flat_map
- [cpp26-features.md](./references/cpp26-features.md) — Reflection, contracts, memory safety improvements
- [compiler-hardening.md](./references/compiler-hardening.md) — Flags, sanitizers, hardened libc++
- [safe-idioms.md](./references/safe-idioms.md) — Security patterns by vulnerability class
@@ -0,0 +1,80 @@
# Anti-Patterns: Legacy to Modern C++
Comprehensive reference of legacy C++ patterns and their modern replacements. Each entry explains WHY the modern version is better — not just that it exists.
## Memory Management
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| `new T` / `delete p` | `std::make_unique<T>()` | C++14 | Eliminates memory leaks, double-free; exception-safe construction |
| `new T[]` / `delete[] p` | `std::vector<T>` or `std::make_unique<T[]>(n)` | C++14 | Automatic sizing, bounds-aware with `.at()` |
| Raw owning `T*` | `std::unique_ptr<T>` | C++11 | RAII ownership; zero overhead vs raw pointer |
| Raw shared `T*` | `std::shared_ptr<T>` via `std::make_shared` | C++11 | Reference-counted lifetime; thread-safe refcount |
| C arrays `int arr[N]` | `std::array<int, N>` | C++11 | Value semantics, `.size()`, no decay to pointer |
| `malloc`/`free` | Containers or smart pointers | C++11 | Type-safe, exception-safe, RAII |
| `memcpy(dst, src, n)` | `std::copy(src, src+n, dst)` or container assignment | C++11 | Type-safe, works with non-trivial types |
| `memset(buf, 0, n)` | Value initialization or `std::fill` | C++11 | No risk of zeroing non-trivially-constructible types |
| Pointer + length parameter pairs | `std::span<T>` | C++20 | Carries size, bounds-checkable with hardened mode |
| `const char*` for non-owning strings | `std::string_view` | C++17 | Carries length, no null-terminator assumption |
| Nullable `T*` for optional values | `std::optional<T>` | C++17 | Explicit intent, no null dereference risk |
## Type Safety
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| C-style cast `(int)x` | `static_cast<int>(x)` | C++11 | Explicit intent, greppable, won't silently reinterpret |
| `reinterpret_cast` for type punning | `std::bit_cast<T>(x)` | C++20 | Defined behavior, `constexpr`-compatible |
| `union` for variants | `std::variant<A, B, C>` | C++17 | Type-safe access via `std::visit`, no silent UB |
| `void*` type erasure | `std::any`, `std::variant`, or templates | C++17 | Type-safe, no manual casting |
| Plain `enum` | `enum class` | C++11 | Scoped, no implicit int conversion |
| `NULL` or `0` | `nullptr` | C++11 | Unambiguous null pointer, no overload confusion |
| Implicit single-arg constructors | Mark `explicit` | C++11 | Prevents surprising implicit conversions |
| Unchecked return values | `[[nodiscard]]` | C++17 | Compiler warns when return value is ignored |
| `= delete` without message | `= delete("reason")` | C++26 | Documents why the overload is forbidden |
## Error Handling
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| Error codes + out-params | `std::expected<T, E>` | C++23 | Composable with `.and_then()`, `.transform()`, `.or_else()` |
| `assert()` macro | `contract_assert` | C++26 | Visible to tooling, configurable enforcement modes |
| Unchecked `errno` | `std::expected` or exceptions | C++23 | Forces caller to handle the error path |
| Throwing in constructor for expected failures | Factory returning `std::expected` | C++23 | No exception overhead for expected failure paths |
## I/O and Formatting
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| `printf` / `sprintf` | `std::format` / `std::print` | C++20/23 | Type-safe, no format string vulnerabilities |
| `snprintf` for string building | `std::format` | C++20 | Returns `std::string`, no buffer management |
| `std::cout << a << b << c` | `std::println("{} {} {}", a, b, c)` | C++23 | Readable, no stream state issues, faster |
## Concurrency
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| `mutex.lock()` / `mutex.unlock()` | `std::scoped_lock` | C++17 | Exception-safe, supports multiple mutexes (no deadlock) |
| `std::thread` + manual `.join()` | `std::jthread` | C++20 | Auto-joins on destruction, supports stop tokens |
| `volatile` for thread sync | `std::atomic<T>` | C++11 | Actually guarantees atomic operations and memory ordering |
| Manual condition variable notify | `std::latch`, `std::barrier` | C++20 | Higher-level, less error-prone synchronization |
| Hand-rolled atomic CAS loops | `std::atomic::fetch_max/fetch_min` | C++26 | Standard, correct, optimized |
## Metaprogramming
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| `std::enable_if` / SFINAE | Concepts + `requires` | C++20 | Readable constraints, clear error messages |
| CRTP for static polymorphism | Deducing `this` | C++23 | No template boilerplate, works with lambdas |
| Macros for code generation | Static reflection | C++26 | Zero-overhead, composable, type-safe |
| `std::tuple_element` for pack access | Pack indexing `T...[N]` | C++26 | Direct access, no recursive template machinery |
| `std::function` for non-owning callbacks | `std::function_ref` | C++26 | No allocation, passable in registers |
## Containers
| Avoid | Use Instead | Standard | Why |
|-------|-------------|----------|-----|
| `std::map` for read-heavy lookups | `std::flat_map` | C++23 | Cache-friendly, 8-17x faster lookup for small-medium maps |
| `boost::static_vector` | `std::inplace_vector` | C++26 | Standard, no Boost dependency, fallible API |
| `std::function` (when non-owning) | `std::function_ref` | C++26 | Zero allocation overhead |
| Raw pointer polymorphism | `std::indirect<T>`, `std::polymorphic<T>` | C++26 | Value semantics, copyable, no manual lifetime management |
| Manual iterator loops | `std::ranges::` algorithms + views | C++20 | Composable, lazy, no off-by-one |
@@ -0,0 +1,241 @@
# Compiler Hardening
Security-focused compiler and linker configuration for C++ projects. Based on the [OpenSSF Compiler Options Hardening Guide](https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html) and Trail of Bits recommendations.
## Essential Compiler Flags
### Warnings
| Flag | Purpose | GCC | Clang |
|------|---------|-----|-------|
| `-Wall` | Core warnings | Yes | Yes |
| `-Wextra` | Additional warnings | Yes | Yes |
| `-Wpedantic` | Strict ISO compliance | Yes | Yes |
| `-Werror` | Treat warnings as errors | Yes | Yes |
| `-Wconversion` | Implicit narrowing conversions | Yes | Yes |
| `-Wsign-conversion` | Signed/unsigned conversion | Yes | Yes |
| `-Wformat=2` | Format string issues | Yes | Yes |
| `-Wunsafe-buffer-usage` | Raw pointer arithmetic | No | Yes |
### Stack Protection
| Flag | Purpose | GCC | Clang |
|------|---------|-----|-------|
| `-fstack-protector-strong` | Stack buffer overflow detection | Yes | Yes |
| `-fstack-clash-protection` | Prevent stack clash attacks | Yes | Yes |
### Fortification
```
-D_FORTIFY_SOURCE=3
```
Adds runtime bounds checks to `memcpy`, `strcpy`, `sprintf`, and other libc functions. Level 3 (GCC 12+, Clang 16+) provides the most coverage.
**Warning:** This flag is silently ignored if optimization is disabled (`-O0`). Always verify it's active:
```bash
# Check that _FORTIFY_SOURCE is defined in preprocessor output
echo '#include <stdio.h>' | gcc -E -dM -D_FORTIFY_SOURCE=3 -O2 - | grep FORTIFY
```
Trail of Bits found [20+ projects on GitHub](https://blog.trailofbits.com/2023/04/20/typos-that-omit-security-features-and-how-to-test-for-them/) where `_FORTIFY_SOURCE` was misspelled (e.g., `-FORTIFY_SOURCE=2` instead of `-D_FORTIFY_SOURCE=2`).
### Auto-Initialization
```
-ftrivial-auto-var-init=zero
```
Zero-initializes all local variables that would otherwise be uninitialized. Eliminates information leaks from stack variables at minimal performance cost.
### Position-Independent Code
```
-fPIE -pie # for executables
-fPIC -shared # for shared libraries
```
Required for ASLR to work effectively.
### Linker Hardening
| Flag | Purpose |
|------|---------|
| `-Wl,-z,relro` | Read-only relocations (partial RELRO) |
| `-Wl,-z,now` | Immediate binding (full RELRO, protects GOT) |
| `-Wl,-z,noexecstack` | Non-executable stack |
### Control Flow Integrity
```
-fcf-protection=full # GCC/Clang on x86 (Intel CET)
```
## Hardened libc++ (Clang/libc++)
libc++ provides hardening modes that add bounds-checking to standard containers with minimal overhead.
### Modes
| Mode | Flag | What It Checks | Overhead |
|------|------|---------------|----------|
| Fast | `_LIBCPP_HARDENING_MODE_FAST` | Bounds on `[]`, `front()`, `back()` | ~0.3% |
| Extensive | `_LIBCPP_HARDENING_MODE_EXTENSIVE` | Fast + iterator validity, internal invariants | Higher |
| Debug | `_LIBCPP_HARDENING_MODE_DEBUG` | All checks including O(n) validations | Significant |
### How to Enable
```
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
```
### Recommendation
- **All builds**: Enable `_FAST` mode — Google measured 0.3% overhead across their fleet
- **Security-critical code**: Enable `_EXTENSIVE` mode
- **Development/testing**: Enable `_DEBUG` mode
### Google's Results
Deployed across Chrome (2022) and entire server fleet:
- ~0.3% average performance overhead
- 1000+ previously undetected bugs found
- 30% reduction in production segfault rate
## Sanitizers
Runtime instrumentation for detecting bugs that static analysis misses.
### AddressSanitizer (ASan)
Detects: heap/stack buffer overflow, use-after-free, double-free, memory leaks.
```
-fsanitize=address -fno-omit-frame-pointer
```
Overhead: ~2x slowdown, ~3x memory. Use in CI, not production.
### UndefinedBehaviorSanitizer (UBSan)
Detects: signed integer overflow, null pointer dereference, division by zero, misaligned access, out-of-bounds array access.
```
-fsanitize=undefined -fno-sanitize-recover=all
```
Overhead: minimal (~5-10%). Safe for most CI runs.
### ThreadSanitizer (TSan)
Detects: data races, lock-order inversions, deadlocks.
```
-fsanitize=thread
```
Overhead: ~5-15x slowdown. Use for concurrent code testing.
### MemorySanitizer (MSan)
Detects: reads of uninitialized memory.
```
-fsanitize=memory -fno-omit-frame-pointer
```
Overhead: ~3x slowdown. Clang-only. Requires all linked code to be instrumented.
### Combining Sanitizers
| Combination | Compatible? |
|-------------|-------------|
| ASan + UBSan | Yes — recommended default |
| ASan + TSan | **No** — mutually exclusive |
| ASan + MSan | **No** — mutually exclusive |
| TSan + UBSan | Yes |
| MSan + UBSan | Yes |
### CI Integration
Recommended CI matrix:
```
1. Build with ASan + UBSan → run all tests
2. Build with TSan → run concurrency tests
3. (Optional) Build with MSan → run all tests (Clang only)
```
### Production: GWP-ASan
For production environments where full ASan is too expensive, use GWP-ASan — a sampling-based allocator that detects heap bugs with near-zero overhead.
- Enabled by default in Chrome, Android, Apple platforms
- Catches use-after-free and buffer overflow in production
- See [Trail of Bits guide to GWP-ASan](https://blog.trailofbits.com/2025/12/16/use-gwp-asan-to-detect-exploits-in-production-environments/)
## Verifying Hardening
Don't trust that flags are applied — verify the binary:
### checksec
```bash
checksec --file=./my_binary
```
Checks: RELRO, stack canary, NX, PIE, FORTIFY.
### readelf
```bash
# Check for RELRO
readelf -l ./my_binary | grep GNU_RELRO
# Check for stack canary symbols
readelf -s ./my_binary | grep __stack_chk
# Check for FORTIFY
readelf -s ./my_binary | grep _chk
```
### Compiler Explorer
For quick verification during development, compile on [Compiler Explorer](https://godbolt.org/) and inspect the generated assembly for canary checks and fortified function calls.
## Complete Hardened Compilation Example
```bash
# GCC
g++ -std=c++23 \
-Wall -Wextra -Wpedantic -Werror -Wconversion \
-D_FORTIFY_SOURCE=3 -O2 \
-fstack-protector-strong -fstack-clash-protection \
-ftrivial-auto-var-init=zero \
-fcf-protection=full \
-fPIE -pie \
-Wl,-z,relro,-z,now -Wl,-z,noexecstack \
main.cpp -o my_binary
# Clang (with hardened libc++)
clang++ -std=c++23 -stdlib=libc++ \
-Wall -Wextra -Wpedantic -Werror -Wconversion \
-Wunsafe-buffer-usage \
-D_FORTIFY_SOURCE=3 -O2 \
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST \
-fstack-protector-strong -fstack-clash-protection \
-ftrivial-auto-var-init=zero \
-fcf-protection=full \
-fPIE -pie \
-Wl,-z,relro,-z,now -Wl,-z,noexecstack \
main.cpp -o my_binary
```
## References
- [OpenSSF Compiler Options Hardening Guide](https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html)
- [libc++ Hardening Modes](https://libcxx.llvm.org/Hardening.html)
- [Trail of Bits: Typos That Omit Security Features](https://blog.trailofbits.com/2023/04/20/typos-that-omit-security-features-and-how-to-test-for-them/)
- [Trail of Bits: Use GWP-ASan in Production](https://blog.trailofbits.com/2025/12/16/use-gwp-asan-to-detect-exploits-in-production-environments/)
- [Google: Retrofitting Spatial Safety](https://security.googleblog.com/2024/11/retrofitting-spatial-safety-to-hundreds.html)
@@ -0,0 +1,268 @@
# C++20 Features (Default Practice)
These features are mature, well-supported (GCC 12+, Clang 14+, MSVC 17.0+), and should be the default way to write C++.
## Concepts
Constrain templates with readable, declarative requirements.
### What They Replace
SFINAE and `std::enable_if` — the arcane template tricks that produced unreadable error messages.
### Standard Concepts
Use concepts from `<concepts>` before writing custom ones:
```cpp
#include <concepts>
template <std::integral T>
T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }
template <std::invocable<int> F>
void apply(F&& fn, int value) { fn(value); }
```
Key standard concepts: `std::integral`, `std::floating_point`, `std::invocable`, `std::copyable`, `std::movable`, `std::regular`, `std::predicate`, `std::ranges::range`.
### Custom Concepts
```cpp
template <typename T>
concept Serializable = requires(T obj, std::ostream& os) {
{ obj.serialize(os) } -> std::same_as<void>;
{ T::deserialize(os) } -> std::same_as<T>;
};
void save(const Serializable auto& obj, std::ostream& os) {
obj.serialize(os);
}
```
### requires Clauses vs concept Keyword
- **`concept`**: reusable, named constraint — define when used in 2+ places
- **`requires`**: inline, one-off constraint
```cpp
// One-off: use requires
template <typename T>
requires (sizeof(T) <= 16)
void small_copy(T value);
// Reusable: define a concept
template <typename T>
concept SmallType = sizeof(T) <= 16;
```
### Before / After
```cpp
// BEFORE: SFINAE — cryptic, fragile
template <typename T,
typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T square(T x) { return x * x; }
// Error: "no matching function for call to 'square'" + 50 lines of template noise
// AFTER: concepts — readable, clear errors
template <typename T>
requires std::is_arithmetic_v<T>
T square(T x) { return x * x; }
// Error: "constraint 'is_arithmetic_v<string>' not satisfied"
```
## Ranges
Composable, lazy algorithms that operate on range objects instead of iterator pairs.
### Key Patterns
```cpp
#include <ranges>
#include <algorithm>
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// View pipeline — lazy, no intermediate allocations
auto result = data
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
// Range algorithms — accept ranges directly
std::ranges::sort(data);
auto it = std::ranges::find(data, 42);
auto [mn, mx] = std::ranges::minmax(data);
```
To materialize a lazy view into a concrete container, use `std::ranges::to<>()` from C++23 — see [cpp23-features.md](./cpp23-features.md).
### Useful Views
| View | Purpose |
|------|---------|
| `views::filter` | Keep elements matching predicate |
| `views::transform` | Apply function to each element |
| `views::take(n)` | First n elements |
| `views::drop(n)` | Skip first n elements |
| `views::split(delim)` | Split range on delimiter |
| `views::join` | Flatten nested ranges |
| `views::enumerate` | Pairs of (index, element) — C++23 |
| `views::zip` | Zip multiple ranges — C++23 |
| `views::chunk(n)` | Group into chunks of n — C++23 |
| `views::concat` | Concatenate ranges — C++26 |
## std::span<T>
Non-owning view over contiguous memory. Replaces pointer + length pairs.
```cpp
#include <span>
// BEFORE: dangerous — no size information
void process(int* data, size_t len);
// AFTER: carries size, bounds-checkable
void process(std::span<int> data) {
for (auto& val : data) { /* safe iteration */ }
data[0]; // unchecked (fast)
data.at(0); // bounds-checked with hardened libc++
}
// Works with any contiguous container
std::vector<int> vec = {1, 2, 3};
std::array<int, 3> arr = {4, 5, 6};
int c_arr[] = {7, 8, 9};
process(vec); // implicit conversion
process(arr); // implicit conversion
process(c_arr); // implicit conversion
```
Use `std::span<const T>` for read-only views.
## std::format
Type-safe string formatting, replacing `sprintf` and iostream chains.
```cpp
#include <format>
std::string s = std::format("Hello, {}!", name);
std::string t = std::format("{:>10.2f}", 3.14159); // right-aligned, 2 decimal
std::string u = std::format("{:#x}", 255); // 0xff
std::string v = std::format("{:%Y-%m-%d}", std::chrono::system_clock::now());
```
### Custom Formatters
```cpp
template <>
struct std::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const Point& p, format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
// Now works:
std::println("Position: {}", my_point);
```
## Three-Way Comparison (<=>)
Generate all comparison operators from one declaration.
```cpp
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
// Generates: ==, !=, <, >, <=, >=
};
```
For custom ordering:
```cpp
struct Version {
int major, minor, patch;
std::strong_ordering operator<=>(const Version& other) const {
if (auto cmp = major <=> other.major; cmp != 0) return cmp;
if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
return patch <=> other.patch;
}
bool operator==(const Version&) const = default;
};
```
## Designated Initializers
Named field initialization for aggregates.
```cpp
struct Config {
int port = 8080;
bool verbose = false;
int max_connections = 100;
};
// Clear which fields are being set
Config cfg = {.port = 3000, .verbose = true};
// .max_connections gets its default value (100)
```
## std::jthread
Thread with automatic joining and cooperative cancellation.
```cpp
#include <thread>
// BEFORE: forgetting to join causes std::terminate
std::thread t(work);
// ... if exception thrown here, t.join() never called → crash
// AFTER: auto-joins on destruction
std::jthread t(work);
// destructor calls request_stop() then join() — always safe
// Cooperative cancellation via stop token
std::jthread worker([](std::stop_token stoken) {
while (!stoken.stop_requested()) {
do_work();
}
});
worker.request_stop(); // signal the thread to exit
// destructor waits for thread to finish
```
## Coroutines
Suspendable functions for lazy computation and async I/O.
### When to Use
- I/O-bound async work (network, file system)
- Lazy sequence generation (use `std::generator` from C++23)
- State machines that would otherwise be complex
### When NOT to Use
- CPU-bound parallel work (coroutines cooperate, they don't parallelize)
- Simple synchronous code (unnecessary complexity)
- Reimplementing coroutine types (`generator`, `task`) that libraries already provide
- Custom awaiters are fine when adapting async libraries to coroutines — that's often the only way to bridge callback-based APIs
### With std::generator (C++23)
```cpp
std::generator<int> range(int start, int end) {
for (int i = start; i < end; ++i)
co_yield i;
}
for (auto val : range(0, 10)) {
std::println("{}", val);
}
```
@@ -0,0 +1,269 @@
# C++23 Features (Usable Today)
These features have solid compiler support (GCC 13+, Clang 17+, MSVC 17.4+) and deliver immediate value. Adopt them now.
## std::expected<T, E>
Type-safe error handling with monadic chaining. The most impactful C++23 feature for everyday code.
### What It Replaces
Error codes + output parameters, nullable returns, exceptions at API boundaries where failure is expected (not exceptional).
### Basic Usage
```cpp
#include <expected>
enum class ParseError { empty_input, invalid_format, overflow };
std::expected<int, ParseError> parse_int(std::string_view s) {
if (s.empty())
return std::unexpected(ParseError::empty_input);
// ... parsing logic ...
return 42;
}
// Caller must handle both paths
auto result = parse_int("123");
if (result) {
use(*result); // or result.value()
} else {
handle(result.error());
}
```
### Monadic Chaining
Chain operations without nested if-checks:
```cpp
std::expected<Config, Error> load_config(const Path& p) {
return read_file(p) // expected<string, Error>
.and_then(parse_toml) // expected<TomlValue, Error>
.and_then(validate_config) // expected<Config, Error>
.transform(apply_defaults); // expected<Config, Error>
}
```
- `.and_then(f)` — if value, call `f(value)` which returns a new `expected`
- `.transform(f)` — if value, call `f(value)` and wrap result in `expected`
- `.or_else(f)` — if error, call `f(error)` which returns a new `expected`
- `.transform_error(f)` — if error, call `f(error)` and wrap result
### When to Use vs Exceptions
- **Use `std::expected`**: expected failures (file not found, parse error, validation failure), performance-critical paths, codebases that ban exceptions
- **Use exceptions**: truly exceptional conditions (out of memory, programmer error), deep call stacks where propagation cost matters
### Compiler Support
GCC 13+, Clang 17+, MSVC 17.4+
## std::print / std::println
Type-safe formatted output. Essentially `fmt::print` standardized.
### Basic Usage
```cpp
#include <print>
std::println("Hello, {}!", name); // with newline
std::print("x = {}, y = {}\n", x, y); // without auto-newline
std::println("{:>10.2f}", 3.14159); // format spec: right-aligned, 2 decimal
std::println("{0} is {0}", value); // reuse argument by index
```
### What It Replaces
```cpp
// BEFORE: printf (type-unsafe, format string vulnerabilities)
printf("Name: %s, Age: %d\n", name.c_str(), age);
// BEFORE: iostream (verbose, stateful, slow)
std::cout << "Name: " << name << ", Age: " << age << std::endl;
// AFTER: type-safe, readable, fast
std::println("Name: {}, Age: {}", name, age);
```
### Relationship to {fmt}
`std::print` IS the `{fmt}` library adopted into the standard, by the same author (Victor Zverovich). If you already use `{fmt}`, switching to `std::print` removes one dependency. `{fmt}` still has extras `std::print` lacks (color output, named arguments).
### Compiler Support
GCC 14+, Clang 18+, MSVC 17.4+
## Deducing this
Eliminates const/non-const overload duplication and simplifies CRTP.
### The Problem It Solves
```cpp
// BEFORE: duplicated logic for const and non-const
class Buffer {
std::vector<char> data_;
public:
char& operator[](size_t i) { return data_[i]; }
const char& operator[](size_t i) const { return data_[i]; }
// Often 4 overloads: const/non-const x lvalue/rvalue
};
// AFTER: one function handles all qualifications
class Buffer {
std::vector<char> data_;
public:
template <typename Self>
auto&& operator[](this Self&& self, size_t i) {
return std::forward<Self>(self).data_[i];
}
};
```
### Simplified CRTP
```cpp
// BEFORE: arcane template pattern
template <typename Derived>
struct Base {
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
// AFTER: no template parameter needed
struct Base {
void interface(this auto&& self) {
self.implementation();
}
};
```
### Recursive Lambdas
```cpp
// BEFORE: std::function or Y-combinator
std::function<int(int)> fib = [&](int n) {
return n <= 1 ? n : fib(n-1) + fib(n-2);
};
// AFTER: direct recursion
auto fib = [](this auto&& self, int n) -> int {
return n <= 1 ? n : self(n-1) + self(n-2);
};
```
### Compiler Support
GCC 14+, Clang 18+, MSVC 19.32+
## std::ranges::to (Materializing Views)
Converts lazy range views into concrete containers. The missing piece from C++20 ranges.
```cpp
#include <ranges>
auto vec = data
| std::views::filter(is_valid)
| std::views::transform(process)
| std::ranges::to<std::vector>(); // materialize into a vector
// Works with any container
auto set = data
| std::views::transform(normalize)
| std::ranges::to<std::set>();
```
Without `std::ranges::to`, you had to manually construct containers from view iterators — awkward and error-prone. This completes the C++20 ranges story.
### Compiler Support
GCC 14+, Clang 17+, MSVC 17.4+
## std::flat_map / std::flat_set
Cache-friendly sorted containers backed by contiguous storage.
### When to Prefer Over std::map
- **Read-heavy workloads** — lookup is 8-17x faster than `std::map` for small-medium sizes
- **Small-to-medium maps** (up to ~1000 elements)
- **Cache-sensitive code** — contiguous memory vs node-based tree
### When NOT to Use
- **Insert-heavy workloads** — insertion is O(n) due to shifting elements
- **Large maps with frequent mutations** — `std::map`'s O(log n) insert wins
- **Iterator stability required** — flat containers invalidate iterators on insert
### Usage
```cpp
#include <flat_map>
std::flat_map<std::string, int> scores;
scores["alice"] = 100;
scores["bob"] = 85;
auto it = scores.find("alice"); // fast binary search on sorted vector
```
### Compiler Support
GCC 15+, Clang 18+, MSVC 17.6+
## std::generator<T>
Lazy coroutine generators for producing sequences on demand.
### Usage
```cpp
#include <generator>
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}
// Lazy — only computes as many values as consumed
for (auto val : fibonacci() | std::views::take(10)) {
std::println("{}", val);
}
```
### Compiler Support
MSVC 17.4+, GCC 14+ (partial), Clang catching up. Wait for broader support before relying on it.
## import std
Import the entire standard library as a module.
```cpp
import std; // replaces ALL #include <...> for standard headers
int main() {
std::println("Hello from modules!");
}
```
### Caveats
- Build system support varies (CMake 3.28+ with some generators)
- Third-party libraries are mostly not module-ready
- Mixing headers and modules requires care
- Best for new projects; migration of existing code is nontrivial
### Compiler Support
GCC 15+, Clang 18+ (partial), MSVC 17.5+
@@ -0,0 +1,238 @@
# C++26 Features
C++26 was finalized in March 2026 — the most significant release since C++11. This reference covers the features worth knowing about, ranked by practical impact.
## Reflection (Game-Changer)
Static reflection is the single most impactful C++26 feature. It lets programs inspect types, members, and functions at compile time and generate code with zero runtime overhead.
### What It Eliminates
- Per-struct serialization functions (JSON, protobuf, XML)
- Code generators (protobuf codegen, Qt MOC, flatbuffers)
- Macro-based registration systems (enum-to-string, type registries)
- Verbose template metaprogramming with `std::tuple` tricks
### Core Syntax
The `^^` operator ("reflection operator") reflects a construct into a `std::meta::info` value:
```cpp
#include <meta>
constexpr auto type_info = ^^int; // reflect the type 'int'
constexpr auto member_info = ^^MyStruct::field; // reflect a member
```
Key `std::meta::` functions:
- `nonstatic_data_members_of(^^T)` — enumerate struct members
- `identifier_of(member)` — get member name as string
- `type_of(member)` — get member type
- `enumerators_of(^^E)` — enumerate enum values
The splice operator `[:expr:]` converts a reflection back into code.
### Before / After
```cpp
// BEFORE: write this for EVERY struct, update when fields change
json to_json(const Player& p) {
return {{"name", p.name}, {"health", p.health},
{"x", p.x}, {"y", p.y}, {"score", p.score}};
}
// AFTER: one function, works for ANY struct, never needs updating
template <typename T>
json to_json(const T& obj) {
json result;
template for (constexpr auto member :
std::meta::nonstatic_data_members_of(^^T)) {
result[std::meta::identifier_of(member)] = obj.[:member:];
}
return result;
}
```
### Enum-to-String (Classic Pain Point)
```cpp
// BEFORE: manual mapping, breaks when you add values
const char* to_string(Color c) {
switch (c) {
case Color::Red: return "Red";
case Color::Green: return "Green";
case Color::Blue: return "Blue";
}
}
// AFTER: works for any enum, automatically
template <typename E>
requires std::is_enum_v<E>
std::string to_string(E value) {
template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
if (value == [:e:])
return std::string(std::meta::identifier_of(e));
}
return "<unknown>";
}
```
### Compiler Support
- **GCC 16** (April 2026): Reflection merged in trunk
- **Clang**: Bloomberg experimental fork ([github.com/bloomberg/clang-p2996](https://github.com/bloomberg/clang-p2996)), mainline catching up
- **MSVC**: Not yet
### When to Use / Not Use
- **Use for**: Serialization, deserialization, ORM mapping, debug printing, enum conversion, logging, plugin systems
- **Don't use for**: Simple code where templates or concepts suffice; reflection adds compile-time complexity
## Contracts
Language-level preconditions, postconditions, and assertions.
### Syntax
```cpp
// Precondition — checked before function body executes
void transfer(Account& from, Account& to, int amount)
pre (amount > 0)
pre (from.balance >= amount)
post (from.balance >= 0)
{
from.balance -= amount;
to.balance += amount;
}
// In-body assertion
void process(std::span<int> data) {
contract_assert(!data.empty());
// ...
}
```
### Enforcement Modes
| Mode | Behavior |
|------|----------|
| `enforce` | Check, call violation handler, terminate if it returns |
| `observe` | Check, call violation handler, continue execution |
| `ignore` | Skip the check entirely |
| `quick_enforce` | Check, terminate immediately (no handler) |
Mode is selected at build time, not per-contract.
### Before / After
```cpp
// BEFORE: preconditions buried in body, invisible to callers
int divide(int a, int b) {
assert(b != 0); // only in debug builds, invisible to IDE
return a / b;
}
// AFTER: preconditions on declaration, visible to callers and tools
int divide(int a, int b)
pre (b != 0);
```
### Honest Limitations
- **No virtual function contracts** — deferred to a future standard
- **Controversial design** — Bjarne Stroustrup has publicly criticized the design
- **Compiler support is experimental** — GCC trunk has it, not production-ready
- **Side effects in contract expressions are unspecified**
### Recommendation
Adopt cautiously. Use for new API boundaries where preconditions should be visible. Don't rewrite existing assert() usage wholesale until compilers mature.
## Memory Safety Improvements
### Erroneous Behavior for Uninitialized Reads
Uninitialized local variable reads are no longer undefined behavior. They become "erroneous behavior" — well-defined (the variable holds a valid but unspecified value) but diagnosable.
**Practical impact:** Compilers can no longer use "this read is UB, so I can optimize away your null check" reasoning. Just recompiling as C++26 gives you this protection.
**Opt-out:** `[[indeterminate]]` attribute for performance-critical code where you can prove the variable is written before read.
### Hardened Standard Library
Bounds-checking preconditions added to `operator[]`, `front()`, `back()`, etc. on `std::vector`, `std::span`, `std::string`, `std::string_view`, `std::array`, `std::optional`, `std::expected`.
**Google's deployment results:**
- Deployed across Chrome and entire server fleet
- ~0.3% average performance overhead
- 1000+ previously undetected bugs found
- 30% reduction in production segfault rate
**Enable today** (no need to wait for C++26):
```
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
```
See [compiler-hardening.md](./compiler-hardening.md) for details.
## Smaller Wins
### #embed
Embed binary data at compile time without external tools:
```cpp
// BEFORE: xxd -i data.bin > data.h (slow, 70+ seconds for 40MB)
static const unsigned char data[] = { 0x89, 0x50, 0x4E, ... };
// AFTER: instant, handled by compiler
static constexpr unsigned char data[] = {
#embed "data.bin"
};
```
GCC 15+ and Clang 19+ support `#embed`.
### std::inplace_vector<T, N>
Fixed-capacity, dynamically-resizable vector with no heap allocation. Replaces `boost::static_vector`.
```cpp
std::inplace_vector<int, 64> small_buf; // max 64 elements, stack-allocated
small_buf.push_back(42); // works like vector
// small_buf.push_back() when full: throws (or use try_push_back for fallible API)
```
### std::function_ref
Non-owning, non-allocating callable wrapper. The `std::string_view` of callables.
```cpp
// BEFORE: std::function allocates, type-erases, is nullable
void for_each(std::span<int> data, std::function<void(int)> fn);
// AFTER: zero allocation, passable in registers
void for_each(std::span<int> data, std::function_ref<void(int)> fn);
```
### std::indirect<T> and std::polymorphic<T>
Value-semantic wrappers for heap-allocated objects:
- `std::indirect<T>` — like `unique_ptr` but copyable (deep copies)
- `std::polymorphic<T>` — adds virtual dispatch with value semantics
### Placeholder Variables
```cpp
auto [x, _, _] = get_triple(); // ignore second and third
std::lock_guard _(mutex); // unnamed RAII guard
for (auto _ : std::views::iota(0, 5)) { /* repeat 5 times */ }
```
### Other Notable Additions
- **Pack indexing** `T...[N]` — direct variadic pack access
- **Saturation arithmetic** — `std::add_sat`, `std::sub_sat`, `std::mul_sat`
- **`= delete("reason")`** — document why an overload is deleted
- **`std::hive`** — bucket container with stable pointers and O(1) insert/erase
- **`<simd>`** — data-parallel SIMD types
- **`<linalg>`** — BLAS-based linear algebra
@@ -0,0 +1,299 @@
# Safe C++ Idioms
Security patterns organized by vulnerability class. Each section explains what exploitable bugs the pattern prevents and what modern C++ features eliminate them.
## Memory Safety
### Ownership: Smart Pointers
Use smart pointers for all owning relationships. Raw pointers are only for non-owning observation.
```cpp
// Unique ownership (default choice)
auto widget = std::make_unique<Widget>(args...);
process(*widget); // borrow via reference
// Shared ownership (only when genuinely shared)
auto shared = std::make_shared<Config>(args...);
auto copy = shared; // reference count incremented
// Non-owning observation
Widget* observer = widget.get(); // does NOT own, must not delete
std::weak_ptr<Config> watcher = shared; // non-owning, expires safely
```
**Selection guide:**
- `std::unique_ptr` — default. Zero overhead vs raw pointer. Use for exclusive ownership.
- `std::shared_ptr` — only when multiple owners genuinely need to extend lifetime. Has overhead (refcount, control block).
- `std::weak_ptr` — observe shared objects without preventing destruction. Use to break cycles.
- Raw `T*` / `T&` — non-owning only. Never `delete` a raw pointer you didn't `new`.
### Non-Owning Views
```cpp
// DANGEROUS: pointer + length — no size information
void process(const char* data, size_t len);
// SAFE: carries size, bounds-checkable
void process(std::span<const char> data);
// DANGEROUS: null-terminated assumption
void log(const char* message);
// SAFE: carries length, works with string and string_view
void log(std::string_view message);
```
**Rules:**
- Function parameters: `std::span<T>` for arrays, `std::string_view` for strings
- Return values: return owned types (`std::vector`, `std::string`), not views
- Never return a `span` or `string_view` to a local
### Optional vs Nullable Pointers
```cpp
// DANGEROUS: null pointer used as "no value"
Widget* find(int id); // caller might forget to check
// SAFE: explicit optionality
std::optional<Widget> find(int id); // caller must unwrap
// Check before use
if (auto w = find(42)) {
use(w.value()); // or *w
}
```
### Value Semantics by Default
Prefer value types over heap-allocated pointer indirection. C++26 adds `std::indirect<T>` and `std::polymorphic<T>` for cases where heap allocation is needed but value semantics are desired.
## Type Safety
### Variant Over Union
```cpp
// DANGEROUS: union — accessing wrong member is UB
union Value { int i; double d; std::string s; }; // UB: string in union
// SAFE: variant — type-checked access
std::variant<int, double, std::string> value;
value = "hello";
// Visit pattern — compiler ensures all types handled
std::visit([](auto&& v) { std::println("{}", v); }, value);
// Throws std::bad_variant_access if wrong type
auto& s = std::get<std::string>(value);
```
### Safe Type Punning
```cpp
// DANGEROUS: reinterpret_cast — undefined behavior for most types
float f = 3.14f;
int bits = *reinterpret_cast<int*>(&f); // UB!
// SAFE: bit_cast — defined behavior, constexpr-compatible
int bits = std::bit_cast<int>(f); // OK, well-defined
```
### Enum Class
```cpp
// DANGEROUS: plain enum — implicit int conversion, leaks names
enum Color { Red, Green, Blue };
int x = Red; // compiles silently
if (Red == 0) // compiles silently
// SAFE: enum class — scoped, no implicit conversion
enum class Color : uint8_t { Red, Green, Blue };
// int x = Color::Red; // compile error
// if (Color::Red == 0) // compile error
auto c = Color::Red; // must use scope
```
### Preventing Misuse
```cpp
// Mark functions where ignoring the return is likely a bug
[[nodiscard]] std::expected<Data, Error> load(const Path& p);
// Prevent implicit conversions
class UserId {
public:
explicit UserId(int64_t id) : id_(id) {}
// UserId u = 42; // compile error — must be explicit
private:
int64_t id_;
};
// Document why an overload is forbidden (C++26)
void process(std::string_view s);
void process(std::nullptr_t) = delete("passing nullptr is a bug — use empty string");
```
## Integer Safety
### Narrowing Detection
```cpp
#include <utility>
void safe_convert(int64_t big_value) {
// CHECK: is the value representable in the target type?
if (std::in_range<int32_t>(big_value)) {
int32_t small = static_cast<int32_t>(big_value);
use(small);
} else {
handle_overflow();
}
}
```
### Saturation Arithmetic (C++26)
```cpp
#include <numeric>
// For signal processing, image manipulation, counters
uint8_t pixel = std::add_sat<uint8_t>(200, 100); // 255, not 44
// Useful for counters that should not wrap
size_t count = std::add_sat(count, increment);
```
### Compiler Warnings
Enable `-Wconversion` and `-Wsign-conversion` to catch implicit narrowing at compile time. These flags alone prevent a significant class of integer bugs.
## Concurrency Safety
### Lock Management
```cpp
// DANGEROUS: manual lock/unlock — exception-unsafe, easy to forget
std::mutex mtx;
mtx.lock();
do_work(); // if this throws, mutex is never unlocked
mtx.unlock();
// SAFE: scoped_lock — exception-safe, supports multiple mutexes
{
std::scoped_lock lock(mtx);
do_work(); // mutex released when lock goes out of scope
}
// Multiple mutexes — deadlock-free (sorted lock acquisition)
std::scoped_lock lock(mutex_a, mutex_b);
```
### Thread Management
```cpp
// DANGEROUS: std::thread — forgetting join() calls std::terminate
std::thread t(work);
// if exception before join → crash
// SAFE: jthread — auto-joins, supports cooperative cancellation
std::jthread t([](std::stop_token stoken) {
while (!stoken.stop_requested()) {
do_work();
}
});
// destructor calls request_stop() then join()
```
### Atomics
```cpp
// DANGEROUS: volatile does NOT provide atomicity or ordering
volatile int counter = 0; // data race in multi-threaded code
// SAFE: atomic with explicit memory ordering
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // specify ordering
```
## Initialization Safety
### constexpr / consteval
Computation at compile time is UB-free by design — the compiler rejects any undefined behavior in constant expressions.
```cpp
constexpr int factorial(int n) {
// If this has UB (e.g., signed overflow), it's a compile error
int result = 1;
for (int i = 2; i <= n; ++i) result *= i;
return result;
}
consteval int must_be_compile_time(int n) {
return factorial(n); // guaranteed compile-time evaluation
}
```
### Always Initialize
```cpp
// DANGEROUS: uninitialized variable
int x; // indeterminate value (erroneous behavior in C++26)
use(x); // bug
// SAFE: initialize at declaration
int x = 0;
int y{}; // value-initialized (zero for scalars)
auto z = compute_value();
```
Enable `-ftrivial-auto-var-init=zero` to catch cases you miss.
### Rule of Five / Zero
```cpp
// Rule of Zero: prefer this — let the compiler generate everything
struct Config {
std::string name;
std::vector<int> values;
// No destructor, copy/move constructors, or assignment needed
// Compiler-generated versions do the right thing
};
// Rule of Five: if you must manage a resource manually
class FileHandle {
int fd_;
public:
~FileHandle();
FileHandle(const FileHandle&);
FileHandle& operator=(const FileHandle&);
FileHandle(FileHandle&&) noexcept;
FileHandle& operator=(FileHandle&&) noexcept;
};
// But prefer RAII wrappers (unique_ptr with custom deleter) over this
```
## Iterator Safety
### Prefer Algorithms Over Raw Loops
```cpp
// RISKY: off-by-one, iterator invalidation
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (should_remove(*it)) {
vec.erase(it); // BUG: invalidates iterator
}
}
// SAFE: erase-remove idiom
std::erase_if(vec, should_remove); // C++20
// SAFE: range-based algorithms
auto results = data
| std::views::filter(is_valid)
| std::views::transform(process);
```
### Iterator Invalidation Awareness
Know which operations invalidate iterators on which containers. When in doubt, use indices or rebuild the container. Trail of Bits' [Itergator](https://github.com/trailofbits/itergator) tool detects iterator invalidation bugs via CodeQL.