mirror of
https://github.com/trailofbits/skills
synced 2026-06-21 14:12:00 +00:00
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:
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../plugins/modern-cpp/skills/modern-cpp
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user