C++ Exception Safety: Basic, Strong, and Nothrow

Exception safety in C++ refers to a set of contractual guarantees ensuring that software remains in a valid state and avoids resource leaks when an exception is thrown. This article explores the concept of exception safety and details the three primary levels recognized by the C++ standard—the basic guarantee, the strong guarantee, and the nothrow guarantee—explaining their requirements and practical implications in robust software design.

What Is Exception Safety?

Exception safety is the design principle that code should behave predictably when runtime errors interrupt normal execution. When a function throws an exception, standard execution terminates abruptly, unwinding the call stack. Without proper exception handling, this process can lead to dangling pointers, memory leaks, unreleased mutexes, or data structures left in broken, inconsistent states.

Exception-safe code prevents these failures by relying on resource acquisition is initialization (RAII) and clearly defined program states. Rather than treating safety as an all-or-nothing property, C++ categorizes exception safety into distinct levels, originally formalized by Dave Abrahams.

The Basic Guarantee

The basic guarantee is the baseline requirement for any well-designed C++ component. When an operation satisfies the basic guarantee:

  • No resources are leaked: All dynamically allocated memory, file descriptors, locks, and handles are properly released during stack unwinding.
  • Invariants are preserved: The state of all affected objects remains internally consistent and valid according to their class invariants.
  • State may change: Although objects are valid, their exact state may not match their pre-operation state. The caller can safely query, reassign, or destroy the object, but cannot assume the data inside it remains unaltered.

If a standard container insertion fails under the basic guarantee, the container will not leak memory and will maintain its internal integrity, but some elements may have been shifted or moved.

The Strong Guarantee

The strong guarantee provides commit-or-rollback semantics, similar to a database transaction. When an operation satisfies the strong guarantee:

  • All-or-nothing execution: If the operation succeeds, all changes are committed. If an exception is thrown, the program state is rolled back exactly to what it was prior to the function call.
  • No side effects on failure: The caller can rely on the fact that no data was modified, added, or removed if an error occurred.
  • Full resource safety: No resources are leaked.

This level is commonly implemented using the "copy-and-swap" idiom: the function performs modifications on a temporary copy, and once all potentially throwing operations succeed, it commits the changes using non-throwing swap operations.

The Nothrow Guarantee

The nothrow (or noexcept) guarantee is the strongest level of safety. It guarantees that an operation will never propagate an exception to the caller. Under this guarantee:

  • Guaranteed completion: The function will always complete successfully or handle all errors internally without letting an exception escape.
  • Critical language requirement: In modern C++, destructors, memory deallocation functions, move constructors, and swap functions should always provide the nothrow guarantee.

If a function marked with the noexcept specifier throws an unhandled exception, std::terminate is invoked immediately, terminating the program. The nothrow guarantee forms the essential foundation that enables both the basic and strong guarantees to be implemented reliably elsewhere in the codebase.