Exception safety and resource-safe design
Apply the no-throw, strong, and basic exception-safety guarantees using RAII and transactional updates.
Learning objectives
- Describe the basic, strong, and no-throw exception-safety guarantees.
- Use RAII to prevent resource leaks during exceptions.
- Design an update that either succeeds fully or leaves the original state unchanged.
- Understand the role and limits of noexcept.
Prerequisites: exceptions, constructors and destructors, RAII, and std::vector. Review Exceptions and error handling and RAII and smart pointers first.
Why exception safety matters
An exception can interrupt a sequence of operations. If a function has already changed half of an object's state, callers may observe an inconsistent object. Exception safety describes what remains true when an operation fails.
Three common guarantees
| Guarantee | Meaning |
|---|---|
| No-throw | The operation does not let exceptions escape. |
| Strong | If it fails, observable state is unchanged (transactional behavior). |
| Basic | If it fails, invariants hold and resources are not leaked, but state may have changed. |
RAII protects resources
C++14 example: std::make_unique was added in C++14.
#include <memory>
void work() {
auto data = std::make_unique<int[]>(100);
// If an exception occurs here, data is still cleaned up.
}When stack unwinding destroys the unique_ptr, its managed allocation is released. Prefer RAII wrappers over manual new/delete and manual cleanup paths.
Strong guarantee with copy-and-swap
#include <vector>
class Values {
std::vector<int> data;
public:
void replace(const std::vector<int>& incoming) {
auto temporary = incoming; // may throw; this object unchanged
data.swap(temporary); // vector swap is non-throwing for this allocator setup
}
};The potentially failing copy happens before the object's state changes. If copying fails, the original vector remains intact. Once the copy succeeds, swap commits the replacement. In generic allocator-aware code, verify the swap's noexcept conditions rather than assuming every swap is non-throwing.
noexcept
void release_resource() noexcept {
// cleanup that must not throw
}noexcept is a contract: if an exception escapes a noexcept function, the program calls std::terminate. Do not add noexcept merely because a function is expected not to fail; ensure its operations really cannot throw or that termination is an intentional policy.
Common mistakes
- Confusing the basic guarantee with no guarantee.
- Assuming RAII prevents every logical partial update.
- Marking a function noexcept while it calls potentially throwing operations without handling them.
- Modifying object state before performing operations that may fail, when the strong guarantee is required.
Practice
- Which guarantee says the object's state is unchanged after failure?
- Why does unique_ptr help during stack unwinding?
- What happens if an exception escapes a noexcept function?
Show solutions
- The strong guarantee.
- Its destructor releases the resource automatically.
std::terminateis called.
Summary
RAII protects resources; exception guarantees protect object invariants and caller expectations. Design strong-guarantee operations by preparing changes before committing them.