An exception policy answers two different questions: how is failure reported, and what remains true when it happens? A catch block alone answers neither. The E family connects failures to class invariants and ownership. This lesson applies E.1, E.4–E.8, E.12, E.15–E.19 and the non-throwing swap rules.
1. Name the guarantee
The basic guarantee retains valid invariants and releases resources, but an operation may have changed state. The strong guarantee leaves the promised state unchanged when the operation fails. A no-throw promise means an exception must not escape; it is not an automatic transaction mechanism.
Name the state covered by the promise. A vector of settings can be restored; a notification already sent to another process cannot be unsent by unwinding the stack. The build's strong guarantee covers Store::values, not every side effect of a user callback.
2. Let owners clean up temporary work
Use local RAII objects for resource ownership. A staged vector owns its memory even if an element operation or callback throws. Stack unwinding destroys the staged state without a bespoke cleanup branch. A reference to the live vector is not staging: writes through it already mutate the promised state.
#include <print>
#include <stdexcept>
#include <vector>
void change(std::vector<int>& values, bool fail) {
auto staged = values;
staged.at(0) = 9;
if (fail) throw std::runtime_error("injected");
values.swap(staged);
}
int main() {
std::vector<int> values{1};
try { change(values, true); } catch (const std::runtime_error&) {}
std::println("{}", values[0]);
}3. Validate before observable work
For a batch of edits, validate every index before invoking a hook. Then prepare the result, apply each staged edit, and commit with a non-throwing operation. The test hook injects failure at each step without depending on timing, allocator internals or a production outage. It may also read the Store, so exposing partial live updates would break the specified observation policy.
The function is single-threaded. Atomic publication in this local value sense does not synchronize another thread, create a database transaction, or make a reentrant callback safe. Those are separate contracts.
4. Use noexcept only for a supported promise
Adding noexcept to an allocating, throwing edit does not restore the old value; an escaping exception calls terminate. Destructors and cleanup operations should complete without failure. A resource that has meaningful close errors may need an explicit close/report operation before a non-throwing destructor.
Catch only where you can add useful context, translate at a boundary, or recover according to policy. Catch exception hierarchies by const reference and preserve the original exception when rethrowing. Do not swallow failures or turn a failed required operation into a success-shaped default.
The course also teaches expected for recoverable domain outcomes. The Guidelines discuss exceptions for required-task failures and systematic error codes in environments that cannot throw. Make that policy deliberate; a return type alone is not the whole error strategy, and embedded no-exception policy does not remove the need for RAII.
Apply it
Implement a staged Store batch update. Test every index before callbacks, inject an exception after each partial staged write, and check the original state. Also inspect the external counter: it demonstrates the limit of the guarantee. Then compare this design with the polymorphic Pipeline's copy/swap assignment and the existing RAII scope-guard exercise.