Exception Safety
What is exception safety, and how can I ensure my code is exception-safe?
Exception safety refers to the ability of code to correctly handle and recover from exceptions without leaving the program in an inconsistent or invalid state. Exception-safe code ensures that resources are properly managed, invariants are maintained, and the program remains in a well-defined state, even in the presence of exceptions.
There are three commonly recognized levels of exception safety:
- Basic guarantee: If an exception is thrown, the program is left in a valid but unspecified state. No resources are leaked, and all invariants are preserved.
- Strong guarantee: If an exception is thrown, the program state remains unchanged, as if the operation had never been attempted. This is also known as "commit or rollback" semantics.
- No-throw guarantee: The operation is guaranteed not to throw any exceptions and always succeeds.
To ensure exception safety in your code, you can follow these guidelines:
- Use RAII (Resource Acquisition Is Initialization) techniques to manage resources. This involves wrapping resource acquisition and release in classes with constructors and destructors, ensuring automatic cleanup in case of exceptions.
- Use standard library containers and algorithms, which provide strong exception safety guarantees.
- Use smart pointers (e.g.,
std::unique_ptr
,std::shared_ptr
) to manage dynamically allocated memory and avoid manual memory management. - Avoid throwing exceptions from destructors, as they may lead to unexpected behavior and resource leaks.
- Use the "copy and swap" idiom for assignment operators to provide strong exception safety.
- Use
noexcept
specifiers to indicate functions that are guaranteed not to throw exceptions. - Handle exceptions at the appropriate level of abstraction and avoid letting exceptions propagate uncontrolled.
Here's an example of exception-safe code using RAII:
#include <iostream>
#include <memory>
class Resource {
public:
Resource() {
std::cout << "Acquiring resource\n";
}
~Resource() {
std::cout << "Releasing resource\n";
}
};
void foo() {
std::unique_ptr<Resource> resource{
std::make_unique<Resource>()};
// Use the resource
throw std::runtime_error("Exception occurred");
}
int main() {
try {
foo();
} catch (const std::exception& ex) {
std::cout << "Caught exception: "
<< ex.what();
}
}
Acquiring resource
Releasing resource
Caught exception: Exception occurred
In this example, the Resource
class represents a resource that needs to be acquired and released. The std::unique_ptr
is used to manage the lifetime of the resource. Even if an exception is thrown within the foo()
function, the destructor of Resource
will be called automatically when the std::unique_ptr
goes out of scope, ensuring proper cleanup and preventing resource leaks.
By following exception safety principles and using appropriate techniques like RAII and smart pointers, you can write robust and exception-safe code in C++.
Exceptions: throw
, try
and catch
This lesson provides an introduction to exceptions, detailing the use of throw
, try
, and catch
.