modern-cpp
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.
Install
npx skills add https://github.com/trailofbits/skills/tree/main/plugins/modern-cpp/skills/modern-cpp
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
git clone https://github.com/trailofbits/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Modern C++
Guide for writing modern C++ using C20, C23, 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 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 and 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_FASTfor ~0.3% overhead bounds-checking - Sanitizers in CI — ASan + UBSan as minimum; TSan for concurrent code
- Warning flags —
-Wall -Wextra -Wpedantic -Werror
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 thanassert(), 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.
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 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::spanover pointer + length for function parameters - Use
std::expectedfor 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/constevalwhere possible (UB-free by design) - Mark functions
[[nodiscard]]when ignoring the return value is likely a bug - Prefer value semantics; use
std::variantoverunion,enum classoverenum - Initialize all variables at declaration
Read Next
- anti-patterns.md — Full legacy-to-modern migration table (30+ patterns)
- cpp20-features.md — Concepts, ranges, span, format, coroutines
- cpp23-features.md — expected, print, deducing this, flat_map
- cpp26-features.md — Reflection, contracts, memory safety improvements
- compiler-hardening.md — Flags, sanitizers, hardened libc++
- safe-idioms.md — Security patterns by vulnerability class
Files (skills)
-
references
-
anti-patterns.md 5.5 KB
# 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++98 | Type-safe, works with non-trivial types | | `memset(buf, 0, n)` | Value initialization or `std::fill` | C++98 | 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++98 | 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++98 | 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 | -
compiler-hardening.md 7.3 KB
# 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) -
cpp20-features.md 7 KB
# 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 by default; bounds-checked under hardened libc++ // (_LIBCPP_HARDENING_MODE). span has no .at() until C++26. } // 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); } ``` -
cpp23-features.md 7 KB
# 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) { // forward_like, not forward: vector::operator[] is not ref-qualified, // so forwarding the object alone would drop the rvalue case return std::forward_like<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+ -
cpp26-features.md 7.5 KB
# 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 -
safe-idioms.md 8.1 KB
# 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.
-
-
SKILL.md 8.5 KB
--- 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
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.