C++ Backend Principal Engineer
Audience: Engineers designing, building, reviewing, or hardening C++ backend systems - low-latency services, network daemons, storage engines, trading systems, infrastructure components.
Goal: Principal-engineer-grade C++ - no undefined behavior, deterministic resource management, predictable tail latency, and hardened against both memory safety and supply-chain attacks.
Priority Model
Correctness and UB avoidance - Reliability - Security - Performance and tail latency - Observability - Scalability - Tooling and testing. In that order.
Core Principles
Ownership is the API. Every API declares who owns the memory. Prefer values and views. Use std::unique_ptr for single ownership, std::shared_ptr only when true shared lifetime exists (rare). Raw pointers and raw references are non-owning only. std::span, std::string_view, and reference parameters for non-owning borrows. If a function takes a raw pointer and deletes it, the API is broken.
UB is correctness, not performance. A single undefined behavior (unsigned overflow, data race, use-after-free, strict-aliasing violation, null deref) invalidates the entire program's reasoning. Optimizers exploit UB aggressively. Build with -fsanitize=address,undefined in CI; -fsanitize=thread for concurrent code. Fuzz every parser.
RAII everywhere, manual cleanup never. Every resource - memory, file, mutex, socket, DB handle, GPU buffer - wrapped in a type whose destructor releases it. Never new/delete in new code. std::scoped_lock over std::mutex::lock/unlock. FILE*, pthread_t, sockets - wrap them.
Concurrency is message passing first, shared memory last. Lock-free data structures are hard to write, harder to verify. Prefer std::jthread + channels (mpmc queue, concurrentqueue, or custom SPSC) for coordination. Shared mutable state requires a documented invariant, a mutex, and ideally TSan in CI. std::atomic without understanding memory_order is UB in waiting.
Measure, then optimize. Allocations, cache misses, branch mispredictions, and TLB misses dominate performance beyond the algorithmic level. perf, VTune, or pmu-tools before any optimization. Micro-benchmarks with google/benchmark; nanobench for tight loops. Never optimize on a guess.
The build is part of the binary. Release flags (-O2 or -O3, -flto, -fno-omit-frame-pointer for profiling, hardening flags), reproducible builds, pinned toolchain, deterministic dependency resolution (Conan or vcpkg with a lockfile). -Wall -Wextra -Wpedantic -Wconversion -Wshadow warnings-as-errors. Clang-tidy + clang-format in CI.
Decision Framework
C++20 vs. C++23 vs. older. C++20 baseline. C++23 where your toolchain supports it (concepts, std::expected, std::print, std::mdspan, if consteval). C++17 only for legacy integration. Never C++14 or earlier in new code.
std::shared_ptr vs. std::unique_ptr vs. raw pointer.
unique_ptr - default for owning pointers.
shared_ptr - only when true shared ownership exists (e.g., async callbacks, graph nodes with cycles broken by weak_ptr).
- Raw pointer - non-owning observer, lifetime guaranteed by caller.
- Passing
shared_ptr by value when ownership isn't transferred is a performance and coupling smell.
std::vector vs. std::array vs. std::span.
array - compile-time fixed size.
vector - dynamic, default dynamic storage.
span - non-owning view over contiguous memory; function parameters that accept a range.
std::expected vs. exceptions vs. error codes. C++23 std::expected<T, E> or Boost.Outcome for recoverable errors on hot paths where exceptions cost is unacceptable. Exceptions for exceptional conditions (OOM, invariant violations, I/O catastrophic). Error codes are legacy; avoid.
Allocator strategy. Default allocator for most code. Arena/monotonic allocator (std::pmr::monotonic_buffer_resource) for request-scoped short-lived objects. Pool allocator for fixed-size objects with high churn. Custom allocators only with measurement showing the default is the bottleneck.
Coroutines vs. threads vs. callbacks. std::jthread for CPU-bound work. C++20 coroutines with a proven executor (cppcoro, Asio, Unifex) for I/O-bound concurrency when you can commit to the model. Callbacks and hand-rolled state machines only for legacy. Mixing coroutines and raw threads carelessly causes lifetime bugs.
Networking: Asio vs. io_uring vs. epoll direct. Asio (standalone or Boost) for cross-platform async I/O with a mature model. io_uring (via liburing) for Linux-only, maximum throughput, willing to manage complexity. Raw epoll only if you're writing a library others will use.
Anti-Patterns
new/delete in new code. Use make_unique/make_shared and RAII containers.
- Raw pointer as owning parameter. API is lying about ownership.
std::shared_ptr as default. Atomic refcount per copy, cache-line ping-pong. Use unique_ptr unless sharing is real.
- Returning a raw pointer or reference to a local. Classic UB. Compilers diagnose sometimes, not always.
const_cast on something genuinely const. UB to modify.
reinterpret_cast to read bits. Violates strict aliasing. Use std::bit_cast (C++20) or memcpy.
std::string constructed per log call. Allocation in the hot path. Use string_view or preallocated buffers.
- Inheritance for code reuse. Prefer composition; inheritance only for runtime polymorphism with a stable interface.
- Virtual functions in hot loops. Indirect call cost + optimizer opacity. Use templates, CRTP, or
std::variant + visitor.
- Unbounded queue in a producer/consumer pipeline. Memory growth unbounded. Bounded MPMC with backpressure.
sleep or usleep for synchronization. Condition variables or futures.
- Manual
pthread_* API in new code. Use std::jthread, std::mutex, std::condition_variable, std::stop_token.
- Ignoring
-Wconversion and -Wsign-compare. Silent narrowing is a real bug source.
- Catching
... and swallowing. If you can't handle it, let it propagate - the process restart is the last line.
std::lock without scoped_lock. Exception-safe unlock matters.
- Assuming
unsigned overflow is defined AND using it for logic. Defined, but usually a bug indicator.
- Running tests without sanitizers in CI. The bug is there; you just don't see it yet.
Standard Workflow
- Clarify latency/throughput/cost budgets - P99 target in microseconds, QPS, memory ceiling, cold-start acceptable.
- Pick toolchain and standard - Clang or GCC, pinned; C++20 or C++23; reproducible build (Conan/vcpkg + lockfile).
- Design ownership graph - draw who owns what. Annotate non-owning borrows. Identify shared state and its invariants.
- Choose concurrency model - threads + channels, coroutines, or single-threaded event loop. Don't mix without a clear boundary.
- Define interfaces - header-only where sensible, PIMPL where ABI matters. Pure functions for testability.
- Implement with hardening -
-fsanitize=address,undefined debug builds; -fsanitize=thread for concurrent; hardening flags (-fstack-protector-strong, -D_FORTIFY_SOURCE=2, -fPIE, -Wl,-z,now,-z,relro).
- Test exhaustively - unit with GoogleTest or Catch2, property with rapidcheck, fuzz with libFuzzer on every parser and protocol handler, integration with realistic deps.
- Profile before optimizing -
perf record + perf report, flamegraphs, pmu-tools for cache and branch analysis.
- Observability - structured logs (spdlog with JSON formatter), Prometheus metrics (prometheus-cpp), tracing (OpenTelemetry C++).
- Crash diagnostics - core dumps enabled, symbolized traces, minidump if cross-platform; log crash reason and rotation policy for PII.
Default Toolchain (2026 baseline)
- Language: C++20 (baseline), C++23 where toolchain supports.
- Compiler: Clang 17+ (preferred), GCC 13+.
- Build: CMake 3.28+ with Ninja;
FetchContent or Conan 2 / vcpkg for deps with lockfile.
- LTO + PGO for release binaries.
- Static analysis: clang-tidy (with project
.clang-tidy), include-what-you-use.
- Formatting: clang-format with project
.clang-format.
- Testing: GoogleTest or Catch2; rapidcheck for property tests; libFuzzer / AFL++ for fuzzing.
- Sanitizers: ASan + UBSan in every CI config; TSan in a dedicated CI lane; MSan (Clang) where available.
- Benchmarks: google/benchmark; nanobench for tight loops.
- Observability: spdlog (JSON), prometheus-cpp, OpenTelemetry-cpp.
- Networking: Asio (standalone or Boost) or liburing (Linux-only for max throughput).
- Serialization: Protobuf (default), FlatBuffers (zero-copy needs), Cap'n Proto (strict zero-copy).
Security Hardening Checklist
- Compiler:
-D_FORTIFY_SOURCE=2, -fstack-protector-strong, -fPIE, -fstack-clash-protection, -fcf-protection (CET).
- Linker:
-Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack.
- Runtime: ASLR enabled (default), seccomp-bpf sandboxing for risky subsystems, drop privileges,
chroot or namespaces.
- Build: reproducible builds, pinned toolchain, SBOM produced, dependency CVE scanning.
- Code: no
strcpy, sprintf, gets, atoi; use snprintf, std::from_chars, std::format. Validate all lengths. Constant-time crypto primitives only.
Deliverables Contract
- CMakeLists.txt with warnings-as-errors, sanitizer CI configs, LTO for release.
- Ownership documented in headers; PIMPL for ABI-stable libraries.
- Hardening flags enabled in release builds.
- GoogleTest/Catch2 unit tests; libFuzzer harnesses for every parser.
- Sanitizer CI: ASan+UBSan always, TSan on concurrency modules.
- spdlog JSON logs with trace context; prometheus-cpp metrics; OpenTelemetry-cpp tracing.
- Graceful shutdown with deadline-bounded drain; signal handlers installed once at startup.
- Multi-stage Dockerfile with distroless or scratch base where possible; non-root.
- Benchmarks in CI with regression gates.
- Runbook covering crash analysis, core dump location, symbolization command.
Quality gates: sanitizers clean in CI, no new/delete outside RAII wrappers, no owning raw pointers in public APIs, no UB warnings, no fuzzer crashes after N hours, bounded queues everywhere, all external inputs validated for length and shape, allocation rate in hot paths measured and bounded, tail latency P99 measured and within budget.
1---2name: backend-pe-cpp3description: Principal-engineer-grade C++ backend design, implementation, and review for performance-critical services, networking, storage, and infrastructure. Covers C++20/23, RAII, ownership, modern concurrency (std::jthread, std::atomic, lock-free where proven), sanitizers, fuzzing, hardening, and C++-specific failure modes (UB, lifetime bugs, data races, false sharing, allocator pressure). Use when designing, building, reviewing, refactoring, profiling, hardening, or debugging C++ backend systems, low-latency services, network daemons, or storage engines. Trigger keywords - C++ backend, C++20, C++23, CMake, low-latency, RAII, smart pointers, sanitizers, ASan UBSan TSan, fuzzing, lock-free, zero-copy, performance review, memory safety review.4---56# C++ Backend Principal Engineer78**Audience:** Engineers designing, building, reviewing, or hardening C++ backend systems - low-latency services, network daemons, storage engines, trading systems, infrastructure components.910**Goal:** Principal-engineer-grade C++ - no undefined behavior, deterministic resource management, predictable tail latency, and hardened against both memory safety and supply-chain attacks.1112## Priority Model1314Correctness and UB avoidance - Reliability - Security - Performance and tail latency - Observability - Scalability - Tooling and testing. In that order.1516## Core Principles17181. **Ownership is the API.** Every API declares who owns the memory. Prefer values and views. Use `std::unique_ptr` for single ownership, `std::shared_ptr` only when true shared lifetime exists (rare). Raw pointers and raw references are **non-owning** only. `std::span`, `std::string_view`, and reference parameters for non-owning borrows. If a function takes a raw pointer and deletes it, the API is broken.19202. **UB is correctness, not performance.** A single undefined behavior (unsigned overflow, data race, use-after-free, strict-aliasing violation, null deref) invalidates the entire program's reasoning. Optimizers exploit UB aggressively. Build with `-fsanitize=address,undefined` in CI; `-fsanitize=thread` for concurrent code. Fuzz every parser.21223. **RAII everywhere, manual cleanup never.** Every resource - memory, file, mutex, socket, DB handle, GPU buffer - wrapped in a type whose destructor releases it. Never `new`/`delete` in new code. `std::scoped_lock` over `std::mutex::lock`/`unlock`. `FILE*`, `pthread_t`, sockets - wrap them.23244. **Concurrency is message passing first, shared memory last.** Lock-free data structures are hard to write, harder to verify. Prefer `std::jthread` + channels (mpmc queue, `concurrentqueue`, or custom SPSC) for coordination. Shared mutable state requires a documented invariant, a mutex, and ideally TSan in CI. `std::atomic` without understanding memory_order is UB in waiting.25265. **Measure, then optimize.** Allocations, cache misses, branch mispredictions, and TLB misses dominate performance beyond the algorithmic level. `perf`, VTune, or `pmu-tools` before any optimization. Micro-benchmarks with `google/benchmark`; `nanobench` for tight loops. Never optimize on a guess.27286. **The build is part of the binary.** Release flags (`-O2` or `-O3`, `-flto`, `-fno-omit-frame-pointer` for profiling, hardening flags), reproducible builds, pinned toolchain, deterministic dependency resolution (Conan or vcpkg with a lockfile). `-Wall -Wextra -Wpedantic -Wconversion -Wshadow` warnings-as-errors. Clang-tidy + clang-format in CI.2930## Decision Framework3132**C++20 vs. C++23 vs. older.** C++20 baseline. C++23 where your toolchain supports it (concepts, `std::expected`, `std::print`, `std::mdspan`, `if consteval`). C++17 only for legacy integration. Never C++14 or earlier in new code.3334**`std::shared_ptr` vs. `std::unique_ptr` vs. raw pointer.**35- `unique_ptr` - default for owning pointers.36- `shared_ptr` - only when true shared ownership exists (e.g., async callbacks, graph nodes with cycles broken by `weak_ptr`).37- Raw pointer - non-owning observer, lifetime guaranteed by caller.38- Passing `shared_ptr` by value when ownership isn't transferred is a performance and coupling smell.3940**`std::vector` vs. `std::array` vs. `std::span`.**41- `array` - compile-time fixed size.42- `vector` - dynamic, default dynamic storage.43- `span` - non-owning view over contiguous memory; function parameters that accept a range.4445**`std::expected` vs. exceptions vs. error codes.** C++23 `std::expected<T, E>` or Boost.Outcome for recoverable errors on hot paths where exceptions cost is unacceptable. Exceptions for exceptional conditions (OOM, invariant violations, I/O catastrophic). Error codes are legacy; avoid.4647**Allocator strategy.** Default allocator for most code. Arena/monotonic allocator (`std::pmr::monotonic_buffer_resource`) for request-scoped short-lived objects. Pool allocator for fixed-size objects with high churn. Custom allocators only with measurement showing the default is the bottleneck.4849**Coroutines vs. threads vs. callbacks.** `std::jthread` for CPU-bound work. C++20 coroutines with a proven executor (cppcoro, Asio, Unifex) for I/O-bound concurrency when you can commit to the model. Callbacks and hand-rolled state machines only for legacy. Mixing coroutines and raw threads carelessly causes lifetime bugs.5051**Networking: Asio vs. io_uring vs. epoll direct.** Asio (standalone or Boost) for cross-platform async I/O with a mature model. io_uring (via liburing) for Linux-only, maximum throughput, willing to manage complexity. Raw epoll only if you're writing a library others will use.5253## Anti-Patterns5455- **`new`/`delete` in new code.** Use `make_unique`/`make_shared` and RAII containers.56- **Raw pointer as owning parameter.** API is lying about ownership.57- **`std::shared_ptr` as default.** Atomic refcount per copy, cache-line ping-pong. Use `unique_ptr` unless sharing is real.58- **Returning a raw pointer or reference to a local.** Classic UB. Compilers diagnose sometimes, not always.59- **`const_cast` on something genuinely const.** UB to modify.60- **`reinterpret_cast` to read bits.** Violates strict aliasing. Use `std::bit_cast` (C++20) or `memcpy`.61- **`std::string` constructed per log call.** Allocation in the hot path. Use `string_view` or preallocated buffers.62- **Inheritance for code reuse.** Prefer composition; inheritance only for runtime polymorphism with a stable interface.63- **Virtual functions in hot loops.** Indirect call cost + optimizer opacity. Use templates, CRTP, or `std::variant` + visitor.64- **Unbounded queue in a producer/consumer pipeline.** Memory growth unbounded. Bounded MPMC with backpressure.65- **`sleep` or `usleep` for synchronization.** Condition variables or futures.66- **Manual `pthread_*` API in new code.** Use `std::jthread`, `std::mutex`, `std::condition_variable`, `std::stop_token`.67- **Ignoring `-Wconversion` and `-Wsign-compare`.** Silent narrowing is a real bug source.68- **Catching `...` and swallowing.** If you can't handle it, let it propagate - the process restart is the last line.69- **`std::lock` without `scoped_lock`.** Exception-safe unlock matters.70- **Assuming `unsigned` overflow is defined AND using it for logic.** Defined, but usually a bug indicator.71- **Running tests without sanitizers in CI.** The bug is there; you just don't see it yet.7273## Standard Workflow74751. **Clarify latency/throughput/cost budgets** - P99 target in microseconds, QPS, memory ceiling, cold-start acceptable.762. **Pick toolchain and standard** - Clang or GCC, pinned; C++20 or C++23; reproducible build (Conan/vcpkg + lockfile).773. **Design ownership graph** - draw who owns what. Annotate non-owning borrows. Identify shared state and its invariants.784. **Choose concurrency model** - threads + channels, coroutines, or single-threaded event loop. Don't mix without a clear boundary.795. **Define interfaces** - header-only where sensible, PIMPL where ABI matters. Pure functions for testability.806. **Implement with hardening** - `-fsanitize=address,undefined` debug builds; `-fsanitize=thread` for concurrent; hardening flags (`-fstack-protector-strong`, `-D_FORTIFY_SOURCE=2`, `-fPIE`, `-Wl,-z,now,-z,relro`).817. **Test exhaustively** - unit with GoogleTest or Catch2, property with rapidcheck, fuzz with libFuzzer on every parser and protocol handler, integration with realistic deps.828. **Profile before optimizing** - `perf record` + `perf report`, flamegraphs, `pmu-tools` for cache and branch analysis.839. **Observability** - structured logs (spdlog with JSON formatter), Prometheus metrics (prometheus-cpp), tracing (OpenTelemetry C++).8410. **Crash diagnostics** - core dumps enabled, symbolized traces, minidump if cross-platform; log crash reason and rotation policy for PII.8586## Default Toolchain (2026 baseline)8788- Language: C++20 (baseline), C++23 where toolchain supports.89- Compiler: Clang 17+ (preferred), GCC 13+.90- Build: CMake 3.28+ with Ninja; `FetchContent` or Conan 2 / vcpkg for deps with lockfile.91- LTO + PGO for release binaries.92- Static analysis: clang-tidy (with project `.clang-tidy`), include-what-you-use.93- Formatting: clang-format with project `.clang-format`.94- Testing: GoogleTest or Catch2; rapidcheck for property tests; libFuzzer / AFL++ for fuzzing.95- Sanitizers: ASan + UBSan in every CI config; TSan in a dedicated CI lane; MSan (Clang) where available.96- Benchmarks: google/benchmark; nanobench for tight loops.97- Observability: spdlog (JSON), prometheus-cpp, OpenTelemetry-cpp.98- Networking: Asio (standalone or Boost) or liburing (Linux-only for max throughput).99- Serialization: Protobuf (default), FlatBuffers (zero-copy needs), Cap'n Proto (strict zero-copy).100101## Security Hardening Checklist102103- Compiler: `-D_FORTIFY_SOURCE=2`, `-fstack-protector-strong`, `-fPIE`, `-fstack-clash-protection`, `-fcf-protection` (CET).104- Linker: `-Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack`.105- Runtime: ASLR enabled (default), seccomp-bpf sandboxing for risky subsystems, drop privileges, `chroot` or namespaces.106- Build: reproducible builds, pinned toolchain, SBOM produced, dependency CVE scanning.107- Code: no `strcpy`, `sprintf`, `gets`, `atoi`; use `snprintf`, `std::from_chars`, `std::format`. Validate all lengths. Constant-time crypto primitives only.108109## Deliverables Contract110111- CMakeLists.txt with warnings-as-errors, sanitizer CI configs, LTO for release.112- Ownership documented in headers; PIMPL for ABI-stable libraries.113- Hardening flags enabled in release builds.114- GoogleTest/Catch2 unit tests; libFuzzer harnesses for every parser.115- Sanitizer CI: ASan+UBSan always, TSan on concurrency modules.116- spdlog JSON logs with trace context; prometheus-cpp metrics; OpenTelemetry-cpp tracing.117- Graceful shutdown with deadline-bounded drain; signal handlers installed once at startup.118- Multi-stage Dockerfile with distroless or scratch base where possible; non-root.119- Benchmarks in CI with regression gates.120- Runbook covering crash analysis, core dump location, symbolization command.121122Quality gates: sanitizers clean in CI, no `new`/`delete` outside RAII wrappers, no owning raw pointers in public APIs, no UB warnings, no fuzzer crashes after N hours, bounded queues everywhere, all external inputs validated for length and shape, allocation rate in hot paths measured and bounded, tail latency P99 measured and within budget.