In http://www.stlport.org/doc/exception_safety.html, DaveAbrahams? proposes three levels of exception guarantees for the StlPort library:
- The Basic Guarantee: "no resources are leaked in the face of exceptions".
- The Strong Guarantee: "if an exception is thrown, the operation has no effects".
- an additional, unnamed guarantee: certain operations, such as destructors, "are forbidden to terminate due to an exception"
Since the publication of this note in 1996, others in the C++ community (most notably
HerbSutter, in several of his books and articles) refined the concept to the following:
- The Basic Guarantee: "any exception raised by an operation will leave the program in a valid state". Any invariants are maintained (the original, resource leak policy is subsumed by the invariant that "all allocated resources have referers; all referers refer to validly allocated resources").
- The Strong Guarantee: "the final observable state effect of any operation is either the original state, or the intended target state" (aka the SamuraiPrinciple).
- The Nofail Guarantee: "the operation will never fail".
Note that these guarantees form a hierarchy: each higher guarantee adds additional requirements to the lower guarantees. The requirements of the Nofail guarantee are a strict superset of the Strong guarantee because the final observable state will
always be the intended target state, and the requirements of the Strong guarantee are a strict superset of the Basic guarantee because either the original state or the intended target state are valid states.
The Basic guarantee is required for every function in a program--anything less than the basic guarantee is a defect.
Implementing these guarantees creates a number of beneficial side effects. For one, responsibility for establishing a function's preconditions is firmly placed on the caller of the function. No exceptions should be thrown from a function for precondition violations; instead, these can be viewed as programming defects (not run-time errors), and can be handled via other mechanisms (like assertions).
Another side effect is that it helps expose potential ExtractMethod refactorings: when a function can make a stronger guarantee for part of its work than for another part, it's time to split that function.
The ExceptionGuarantee paradigm hinges on a number of other paradigms:
- A "function" is generically defined as a "unit of work"
- An "error" is defined as a state in which:
- A function can not achieve its own preconditions, or
- A function can not achieve its callee's preconditions, or
- A function can not achieve its own invariants before completing (it's ok to violate them between invocation and return, though), or
- A function can not achieve its own postconditions.
- A function that detects an error is responsible for reporting that error
- The preferred mechanism for reporting errors is the exception.
If your software development method doesn't match up with these paradigms, it's often difficult to apply the
ExceptionGuarantee paradigm (for example, idiomatic
CeeLanguage,
PythonLanguage,
PerlLanguage, and
SmallTalk don't seem to match up well).
The error case of "a function can not achieve its own preconditions" is somewhat slippery. This usually should be covered in the caller's definition of an error, and in earlier embodiments of ExceptionGuarantees, this was the case. However, occasionally a function can have preconditions that can't easily be detected or checked by its caller, so this "escape hatch" was granted for only those cases.
Personally, I don't like the introduction of this loophole. It's a CodeSmell that seems to ask for ExtractMethod. --TimLesher
Note that asserting (terminating the program) is not considered to violate the exception guarantees--even though the program comes to a grinding halt. Asserting is done (in the StlPort context above) as a diagnostic for incorrect use of a library (failure to satisfy preconditions that can be detected by a caller), not as a diagnostic for defects discovered within a library.
CategoryException