What you'll learn
Fast, portable, secure C++ begins with defined behavior and evidence. You will design benchmarks, profile optimized builds, improve algorithms and data layout, isolate platform variation, harden untrusted-input boundaries, and verify dependencies and releases.
By the end, you'll be able to:
- Measure representative workloads without benchmarking noise or dead code
- Prioritize complexity, allocation, locality, copies, and contention
- Build portable feature boundaries across compilers and operating systems
- Apply bounds, integer, lifetime, fuzzing, dependency, and release security practices
Benchmark and profile first
A benchmark answers one narrow question with representative data, an optimized build, warmup, repeated samples, and a statistic such as median plus spread. A profiler locates where the real application spends CPU time, allocations, cache misses, locks, or I/O waits. Measure the baseline and preserve correctness tests before optimizing.
Question: Which JSON lookup strategy lowers p95 request latency?
Workload: production-shaped document sizes and key distributions
Build: optimized, assertions policy recorded, symbols available to profiler
Controls: fixed inputs, warmed caches (or deliberately cold), isolated machine
Evidence: median, p95, variance, CPU profile, allocation count, peak memory
Guardrail: identical parsed results and error behavior
Decision: accept only if the end-to-end improvement exceeds noise and complexity costKey idea
Algorithms, allocation, and locality
An algorithmic improvement can dominate any instruction-level tweak. After complexity, study data layout, access order, allocation frequency, working-set size, and branch predictability. Contiguous structures often outperform pointer-rich structures by using cache lines and prefetching effectively.
| Observation | Possible experiment |
|---|---|
| Repeated vector growth | Reserve from a validated size estimate |
| Many tiny allocations | Store values contiguously or use a measured arena lifetime |
| Linear key lookup dominates | Evaluate ordered or unordered associative lookup |
| Cold fields pollute hot loop | Separate hot data from rarely used metadata |
| Copies dominate | Clarify ownership and measure pass-by-value/move alternatives |
| Lock contention dominates | Partition state or transfer ownership instead of weakening atomics |
Watch out
Compiler and link optimization
Release optimization enables inlining, vectorization, dead-code elimination, and other transformations that assume defined behavior. Link-time optimization sees across translation units. Profile-guided optimization uses representative execution profiles. Inspect generated code only after profiles identify a narrow hot region and source-level changes are insufficient.
- Keep debug symbols available in optimized profiling builds
- Compare wall time and relevant hardware counters, not assembly aesthetics
- Verify floating-point requirements before enabling aggressive math transformations
- Measure binary size and startup when enabling LTO
- Train PGO with production-representative workflows and version the profile process
Portability boundaries
Portable code uses standard fixed-width or least-width integers when representation matters,filesystem::path for paths, chrono for durations, feature-test macros for library support, and toolchain files for target environments. Platform-specific code belongs behind a small adapter with the same tests on each supported target.
#include <iostream>
#include <version>
int main() {
#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
std::cout << "std::expected available\n";
#else
std::cout << "use the project's C++20 result abstraction\n";
#endif
}- Define supported compiler, standard-library, OS, architecture, and endianness tiers
- Build with at least two compiler families when practical
- Avoid assuming char signedness, native path encoding, data-model widths, or byte order
- Test packaging and runtime dependencies on clean target machines
Note
Bounds, integers, lifetimes, and formats
Security failures often begin at trust boundaries: lengths become allocations, offsets overflow, views outlive buffers, or format strings interpret untrusted text. Validate before conversion or allocation, use spans and containers to carry bounds, keep ownership explicit, and use type-safe formatting rather than treating input as a format.
| Risk | Control |
|---|---|
| Integer overflow | Check before arithmetic; use suitable unsigned sizes at byte boundaries |
| Out-of-bounds access | Span/container bounds, checked parsing, fuzzing, sanitizers |
| Use-after-lifetime | RAII ownership, value returns, narrow borrow scopes |
| Format-string injection | Constant format strings and type-safe formatting APIs |
| Path traversal | Resolve under an allowed root and reject escaping components |
| Concurrency race | Ownership partition, mutex contracts, atomics only with a proof |
#include <cstddef>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <vector>
std::vector<std::byte> allocate_records(std::uint32_t count, std::uint32_t width) {
constexpr std::size_t maximum_bytes{16 * 1024 * 1024};
if (width != 0 && count > maximum_bytes / width) {
throw std::length_error{"record block too large"};
}
const std::size_t bytes{static_cast<std::size_t>(count) * width};
return std::vector<std::byte>(bytes);
}Fuzzing, dependencies, and releases
Coverage-guided fuzzing repeatedly mutates inputs to reach new parser paths; sanitizers turn hidden memory and undefined behavior into actionable failures. Keep the harness deterministic, fast, and focused. Dependencies expand the attack surface, so inventory, pin, scan, update, and license them deliberately.
- Fuzz every untrusted parser with realistic maximum-size policies
- Preserve minimized crashing inputs as regression tests
- Generate a software bill of materials for released dependencies
- Verify source and artifact integrity and protect release credentials
- Remove unused dependencies and features to reduce attack surface
- Ship hardened compiler/linker settings appropriate to each platform
- Publish a vulnerability reporting and supported-version policy
Tip
Recap & quick check
Key takeaways
- Optimization begins with a production-shaped benchmark, an optimized build, and profiler evidence.
- Complexity, allocation, locality, copies, and contention usually matter before instruction tweaks.
- Portability requires explicit supported targets and narrow platform adapters.
- Secure C++ validates sizes and arithmetic before allocation, access, conversion, or formatting.
- Fuzzing, sanitizer runs, dependency inventory, and hardened release practices protect boundaries over time.
Quick check
1. Which build should normally be profiled for performance?
2. What often matters before instruction-level tuning?
3. How should platform-specific code be organized?
4. What must happen before multiplying untrusted count by width?
Phase 5 complete. Next, Module 30 — Modern C++ Capstone Projects turns the complete curriculum into portfolio evidence.