Audacity  3.0.3
Exception safety guarantees

Principles of robust error recovery

Weak guarantee

An operation providing a weak guarantee ensures that in case of errors or exceptions, related objects remain in a consistent, but unspecified, state. What constitutes "consistency" varies but at minimum, each object can be safety destroyed or reassigned.

A collection of cooperating objects, each providing a strong guarantee for all of its methods, might only implement a weak guarantee for the higher level of organization.

Partial guarantee

Intermediate between strong and weak, an operation may guarantee in case of errors and exceptions some less than total degree of preservation of prior data against loss, described in documentation comments.

Strong guarantee

An operation providing a strong guarantee ensures that in case of errors or exceptions, all related objects remain in the same state as they were in at the beginning.

"Same" might refer only to logical contents, not necessarily bitwise, physical identity.

This is not the strongest guarantee – that would be no-fail.

Strong guarantee for compound operations

To reassign a complex object with a strong exception safety guarantee, the copy-and-swap C++ idiom is often used:

  • A temporary copy is built, risking exceptions at multiple places.
  • If a later stage of this construction fails, no-throw destructors for the earlier stages are invoked to reclaim resources allocated to the partial object.
  • Only after all possibility of exceptions, a no-throw swap changes the contents of *this.
  • The no-throw destructor of the temporary destroys the old contents.

If the swap is never reached, then the contents of *this are left unchanged.

What is described next can be seen as a generalization of that idiom. To provide a strong guarantee of a compound operation, another common pattern is this:

  1. for each step, implement a separate strong guarantee in case of failure,
  2. for each step (except perhaps the last), implement a no-fail rollback operation that can undo it even after success,
  3. initialize a variable, typically called success or committed, to false,
  4. write one or more finally blocks that invoke the rollbacks if the variable remains false,
  5. invoke the sequence of fallible, strongly guaranteed steps,
  6. only now, at the point of assured success, set the variable to true, so that the finally blocks do nothing, and invoke other finalization steps, which must be no-fail.

Steps 4 and 5 might interleave, but it is very important to order all of step 5 before all of step 6.

No-throw guarantee

An operation providing a no-throw guarantee will never allow an exception to escape, unless for certain conditions such as memory exhaustion that the program treats as absolutely unrecoverable.

This guarantee refers only to the programming language technicality of exception propagation, and is not the same as the stronger No-fail guarantee.

This is almost always a requirement for a C++ destructor. This includes a lambda as argument to the function template finally, which generates an ad hoc destructor.

If a class has a swap operation, that too should be no-throw.

No-fail guarantee

An operation providing a no-fail guarantee will never indicate failure by any means, whether that be an error return code or an escaping exception. It will succeed unless in case of absolutely unrecoverable contingencies such as memory exhaustion or power failure.

No-fail guarantees are often necessary in functions that perform a final phase commit of a collection of related changes or that perform rollbacks of unsuccessful changes – whether for a database in the strict sense or analogously.

Mixed guarantees

Some operations may provide a mix of guarantees: such as, that some condition will hold without fail whether the operation finishes normally or exceptionally; yet, only weak or strong guarantees are given with respect to other conditions. Documentation comments should describe details.