Strong Exception Safety in C++

Strong exception safety means commit or rollback:

  • If an operation succeeds, all changes become visible.

  • If an operation throws, observable program state remains exactly as before.

This article organizes the core guarantees and shows practical C++ patterns to get strong exception safety without overengineering.

1) The exception-safety levels

In practice, code falls into one of these levels:

  1. No guarantee

    • An exception may leak resources or leave objects in an invalid state.

  2. Basic guarantee

    • No resource leaks, invariants are preserved, but object value may change.

  3. Strong guarantee

    • Operation is transactional: either full success or no visible effect.

  4. No-throw guarantee (noexcept)

    • Operation never lets exceptions escape.

noexcept is the strongest contract, but it is usually feasible only for a subset of operations (destructors, swaps, many moves, cleanup paths).

2) The classic strong-guarantee technique: copy-and-swap

For assignment-like updates, copy-and-swap is the most common pattern.

Why it works:

  • Potentially-throwing work happens while constructing rhs.

  • After that, swap is noexcept and commits atomically from caller perspective.

3) std::move, throwing moves, and std::move_if_noexcept

std::move itself never throws; it is only a cast. What can throw is the move constructor/assignment of the target type.

This matters for containers during reallocation. To keep strong guarantees, implementations often prefer copy when move is not noexcept.

Guideline: mark move operations noexcept when you can truly guarantee it. This enables better performance and helps containers preserve strong guarantees.

4) RAII is the foundation

Strong/basic guarantees rely on reliable cleanup. RAII is the standard mechanism.

If an exception is thrown, stack unwinding destroys local objects and releases resources automatically.

5) Destructor and catch guidance

  • Destructors should not throw. If cleanup might throw, catch inside the destructor.

  • Catch exceptions only when you can recover meaningfully.

  • Otherwise, let exceptions propagate to a layer that can decide policy.

6) Practical checklist

  • Define class invariants first; guarantees are meaningless without invariants.

  • Prefer value types and RAII members (std::string, std::vector, smart pointers).

  • Use copy-and-swap for transactional updates.

  • Make swap and move operations noexcept where correct.

  • Use std::move_if_noexcept in generic relocation/update code.

References

Last updated