Plast interview

15. Exception Guarantees

In a low-latency order book, any exception must leave state consistent and quickly recoverable. A misplaced write before a throwing operation can silently degrade correctness under load. Evaluate and fix the guarantees for this hot-path update.

struct Book { std::vector<int> v; bool dirty=false; };
void add(Book& b, int x) {
  b.dirty = true;
  b.v.push_back(x);
}

Part 1.

What exception-safety guarantee does add currently provide, and how would you minimally change it to provide the strong guarantee? When, if ever, is it correct to make add no-throw?

Part 2.

(1) What guarantee does add provide if push_back throws?

(2) How can reordering operations strengthen guarantees here? Any pitfalls?

(3) When is marking add noexcept correct, and when is it dangerous?

(4) How does pre-reserving capacity impact guarantees and latency?

(5) How would you test exception safety without distorting hot-path performance?

Answer

Answer (Part 1)

As written, add only provides the basic guarantee: resources are intact and Book remains usable, but dirty may be spuriously set. For strong guarantee, commit the observable flag after the potentially-throwing operation:

void add(Book& b, int x) {
  b.v.push_back(x);
  b.dirty = true;
}

Declare noexcept only when you can prove no allocation (e.g., pre-reserved capacity) and element operations are non-throwing; otherwise noexcept risks std::terminate on rare failures.

Answer (Part 2)

(1) Basic guarantee: no leaks, object valid, but dirty == true despite no append. Not strong, not no-throw.

(2) Perform push_back first, then set dirty to make the change atomic. Ensure any other invariants aren’t partially exposed earlier.

(3) Correct only if you can guarantee no throws (capacity suffices, T moves/copies are noexcept). Dangerous otherwise: it converts failures into termination.

(4) Pre-reserving improves strong/no-throw prospects by avoiding allocations and reduces jitter. The reserve itself can throw and increases memory footprint.

(5) Use fault-injection or a throwing allocator to simulate failures. Benchmark performance separately with exceptions disabled to avoid skew.