Let's look at memory on the hot path. In earlier chapters we treated the heap like a cafeteria tray system that can get expensive. Here the rule gets stricter: on the runway, do not stop mid-race to build a new lane.
Avoid Heap Allocation on the Hot Path
Heap allocation can look fine in isolation. In a per-message loop it can inject jitter from:
- Allocator locks or contention
- Thread-local allocator refills
- Cache misses
- Memory fragmentation
- Page faults
- Deallocation work
- Allocator implementation changes
Avoid this in a per-message loop:
Prefer automatic storage when you can:
For bounded object lifetimes, use fixed-capacity pools or arenas.
Member order is part of the layout story: you always read next_ before touching storage_[0], so keep them close. This simple pool is appropriate only when:
- Objects do not need arbitrary individual deallocation
- The maximum capacity is known
- Reset timing is safe
- Object lifetime requirements are simple
Fancier pools need careful ownership and reuse rules. Reusing memory while another thread still reads it is a data race with a fancy resume.
The Reallocation Pitfall: `std::string`, `std::vector`, and Capacity
Standard-library containers are useful. The issue is uncontrolled growth in latency-sensitive paths.
This can allocate:
Avoid surprise reallocations by reserving capacity during initialization:
But reserve is not a lifetime warranty. Grow past the reserved capacity and the vector may allocate again. That is the Reallocation Pitfall.
For strict bounded behavior, use fixed-capacity containers or enforce an explicit capacity check:
Now the problem shifts from “unexpected allocation” to “what happens when capacity is exhausted?” That behavior must be explicit:
- Reject the event
- Trigger recovery
- Drop noncritical telemetry
- Stop quoting
- Raise an operational alert
- Fail closed for risk-sensitive operations
Exceptions and the Hot Path
Exceptions are valuable in many C++ applications. In a latency-critical event loop they are often avoided because the exceptional path is hard to bound and unwinding is expensive.
Use explicit error results:
For functions expected not to throw:
Be precise with noexcept. If a function marked noexcept throws, the program calls std::terminate(). Use it when termination is the intended response or when you can prove the function cannot throw.
A common pattern is:
- Hot path: explicit error codes or status types
- Initialization and configuration: exceptions may be acceptable
- Administrative services: normal application error handling
- Fatal invariant violations: controlled fail-safe behavior and alerts
The Takeaway
Preallocate, bound capacity, and keep exceptions off the runway. When something is full, decide the policy before the market decides it for you.
Practice: rewrite a tiny event buffer once with std::vector::reserve and once with FixedVector, then write down what happens at capacity for each.