Exception Handling
Exception Safety
Write robust code that handles exceptions without leaking resources or corrupting states using RAII and exception guarantees.
Interview: Understanding the three guarantees (basic, strong, no-throw), RAII as the base tool, and using copy-and-swap to implement the strong guarantee.
Exception Safety ensures that when an exception is thrown, the program does not leak resources (like memory or file handles) and maintains a consistent state. It is classified into four levels of guarantees.
Basic Guarantee
If an exception is thrown, no resources are leaked, and all objects remain in a valid, destructible state. Program invariants are not corrupted.
Strong Guarantee
If an exception is thrown, the program state is rolled back to exactly what it was before the call (transactional: success or no change).
No-Throw Guarantee
The function will never throw an exception. Critical operations like swap, destructors, and move operations must provide this.
No Guarantee
The program is left in an undefined or corrupted state, and resources may be leaked. This is unacceptable in production code.
Implementing the Strong Guarantee: Copy-and-Swap
To achieve the Strong Exception Safety Guarantee, developers often use the "copy-and-swap" idiom. Instead of modifying the object directly:
- Create a local copy of the target object.
- Perform the risky, throwing operations on the copy.
- If successful, swap the state of the current object with the copy using a non-throwing swap operation.
- If an exception is thrown, the temporary copy is cleaned up automatically, and the original object remains unchanged.
Code Walkthrough
This class uses copy-and-swap to guarantee strong exception safety on assignment.
#include <iostream> #include <vector> #include <algorithm>class SafeArray { private: int* m_data = nullptr; size_t m_size = 0;
public: SafeArray(size_t size) : m_data(new int[size]()), m_size(size) {} ~SafeArray() { delete[] m_data; }
// Copy Constructor (might throw) SafeArray(const SafeArray& other) : m_data(new int[other.m_size]), m_size(other.m_size) { std::copy(other.m_data, other.m_data + other.m_size, m_data); }
// Non-throwing Swap (no-throw guarantee) friend void swap(SafeArray& first, SafeArray& second) noexcept { using std::swap; swap(first.m_data, second.m_data); swap(first.m_size, second.m_size); }
// Assignment Operator providing Strong Exception Safety SafeArray& operator=(SafeArray other) { // Passed by value, creates a copy (might throw) swap(*this, other); // Swaps with temp copy (no-throw) return *this; // Temp object containing old data destroyed here safely } };
int main() { SafeArray a(5); SafeArray b(10); a = b; // Assignment is exception-safe! return 0; }
Interview-Relevant Information
Q: How does RAII ensure the Basic Exception Safety Guarantee?
Answer: Under RAII, raw resources are wrapped inside manager objects (like smart pointers or file guards). If an exception is thrown, stack unwinding automatically triggers destructors of local manager objects, releasing the raw resources. This prevents resource leaks and guarantees basic safety.
Q: What is the relationship between swap and the strong exception safety guarantee?
Answer: The copy-and-swap idiom performs all throwing work on a temporary object. Once successful, the temporary's state is swapped with the target object's state. For the transaction to succeed without failing halfway, the swap function itself must provide the no-throw guarantee.
Quick Checklist
Are you wrapping raw resource pointers in smart pointers? Does your assignment operator use copy-and-swap? If yes, you are writing exception-safe C++ code.
Use Cases
Managing database connection state and transaction rollbacks if queries fail.
Designing container classes (like vectors) where size operations must be transactional.
Common Mistakes
Manually invoking delete in catch blocks, which can be bypassed if exceptions propagate before the delete statement runs.
Modifying object state before throwing operations complete, leaving the object half-updated and invalid.