CodeRigor

FREE LESSON

Core Guidelines: exception guarantees and commit points

State the basic, strong and no-throw guarantees; stage fallible work under RAII and commit without changing promised state on failure.

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.

The strong guarantee needs a commit boundary
All work that might fail happens before the live value is replaced.
The strong guarantee needs a commit boundary
All work that might fail happens before the live value is replaced.

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.

A failure destroys only the temporary
The original remains observable until commit. Cleanup is performed by the temporary owner.
A failure destroys only the temporary
The original remains observable until commit. Cleanup is performed by the temporary owner.
C++
#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.

External effects have their own policy
A callback can increment an external counter before throwing. That count is not part of the Store value guarantee.
External effects have their own policy
A callback can increment an external counter before throwing. That count is not part of the Store value guarantee.

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.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Watch for the design rationale; use the authored lesson and current guideline links for the precise exercise contract.