Exception safety
Exception safety is the state of code working correctly when exceptions are thrown.[1] To aid in ensuring exception safety, C++ standard library developers have devised a set of exception safety levels, contractual guarantees of the behavior of a data structure's operations with regards to exceptions. Library implementers and clients can use these guarantees when reasoning about exception handling correctness. The exception safety levels apply equally to other languages and error-handling mechanisms.[2]
History
As David Abrahams writes, "nobody ever spoke of 'error-safety' before C++ had exceptions."[3] The term appeared as the topic of publications in JTC1/SC22/WG21, the C++ standard committee, as early as 1994.[4] Exception safety for the C++ standard library was first formalized for STLport by Abrahams, establishing the basic safety/strong safety distinction.[5] This was extended to the modern basic/strong/nothrow guarantees in a later proposal.[6]
Background
Exceptions provide a form of non-local control flow, in that an exception may "bubble up" from a called function. This bubbling can cause an exception safety bug by breaking invariants of a mutable data structure, as follows:[7]
- A step of an operation on a mutable data structure modifies the data and breaks an invariant.
- An exception is thrown and control "bubbles up", skipping the rest of the operation's code that would restore the invariant
- The exception is caught and recovered from, or a
finally
block is entered - The data structure with broken invariant is used by code that assumes the invariant, resulting in a bug
Code with a bug such as the above can be said to be "exception unsafe".[7]
Classification
The C++ standard library provides several levels of exception safety (in decreasing order of safety):[8]
- No-throw guarantee, also known as failure transparency: Operations are guaranteed to succeed and satisfy all requirements even in exceptional situations. If an exception occurs, it will be handled internally and not observed by clients.
- Strong exception safety, also known as commit or rollback semantics: Operations can fail, but failed operations are guaranteed to have no side effects, leaving the original values intact.[9]
- Basic exception safety: Partial execution of failed operations can result in side effects, but all invariants are preserved. Any stored data will contain valid values which may differ from the original values. Resource leaks (including memory leaks) are commonly ruled out by an invariant stating that all resources are accounted for and managed.
- No exception safety: No guarantees are made.
Usually, at least basic exception safety is required to write robust code. Higher levels of safety can sometimes be difficult to achieve, and might incur an overhead due to extra copying. A key mechanism for exception safety is a finally
clause, or similar exception handling syntax, which ensure that certain code is always run when a block is exited, including by exceptions. Several languages have constructs that simplify this, notably using the dispose pattern, named as using
, with
, or try
-with-resources.
Example
Consider a smart vector type, such as C++'s std::vector
or Java's ArrayList
. When an item x
is added to a vector v
, the vector must actually add x
to the internal list of objects and update a count field that says how many objects are in v
. It may also need to allocate new memory if the existing capacity isn't sufficient.
Exception safety alternatives:
- No-throw guarantee
- Implemented by ensuring that memory allocation never fails, or by defining the
insert
function's behavior on allocation failure (for example, by having the function return a boolean result indicating whether the insertion took place). - Strong exception safety
- Implemented by doing any necessary allocation first, and then swapping buffers if no errors are encountered (the copy-and-swap idiom). In this case, either the insertion of
x
intov
succeeds, orv
remains unchanged despite the allocation failure. - Basic exception safety
- Implemented by ensuring that the count field is guaranteed to reflect the final size of
v
. For example, if an error is encountered, theinsert
function might completely deallocatev
and reset its count field to zero. On failure, no resources are leaked, butv
's old value is not preserved. - No exception safety
- An insertion failure might lead to corrupted content in
v
, an incorrect value in the count field, or a resource leak.
References
- Crichton, Alex (24 July 2015). "Rust RFC: Stabilize catch_panic". The Rust Programming Language. Retrieved 26 May 2022.
Code is exception safe if it works correctly even when the functions it calls into throw exceptions.
- Lau, Ron (10 November 2020). "Exception safety in JS world". Medium.
- Dave Abrahams (2000). Exception-Safety in Generic Components. Generic Programming. Lecture Notes in Computer Science. Vol. 1766. Springer. pp. 69–79. doi:10.1007/3-540-39953-4_6. ISBN 978-3-540-41090-4. Retrieved 2008-08-29.
- Colvin, Gregory (1994). "Exception Safe Exceptions" (PDF). C++ Standards Committee Papers. Retrieved 17 December 2021.
- Abrahams, David. "STLport: Exception Handling". www.stlport.org. Retrieved 17 December 2021.
- Abrahams, Dave; Colvin, Greg. "Making the C++ Standard Library Exception Safe" (PDF). C++ Standards Committee Papers. Retrieved 17 December 2021.
- Crichton, Alex (24 July 2015). "Rust RFC: Stabilize catch_panic". The Rust Programming Language. Retrieved 26 May 2022.
- Bjarne Stroustrup (1997). Appendix E: Standard-Library Exception Safety in "The C++ Programming Language" (PDF) (3rd ed.). Addison-Wesley. ISBN 0-201-88954-4.
- Austern, Matt (30 May 1997). "Standard Library Exception Policy". C++ Standards Committee Papers. Retrieved 26 May 2022.
External links
- Herb Sutter: Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions, 2000
- Jon Kalb: Exception-Safe Coding in C++, with C++Now! 2012 presentations on exception safety.
- Related discussion on Stackoverflow: C++: do you (really) write exception safe code