Exceptions and Error Handling in C++
An exception in C++ is an event that occurs during the execution of a program that disrupts the normal flow of instructions. It usually signals an error or an unexpected situation — such as division by zero, invalid memory access, or failure to open a file.
Exception handling provides a structured way to detect and respond to these events, separating error-handling code from regular logic. This improves clarity, maintainability, and safety in programs.
How Exceptions Work
When an exception is thrown:
- The normal execution of the program stops immediately.
- Control is transferred to the nearest matching
catchblock. - If no matching handler is found, the program terminates by calling
std::terminate().
Basic Syntax
try {
// Code that might throw an exception
throw std::runtime_error("Something went wrong");
}
catch (const std::runtime_error &e) {
std::cout << "Caught error: " << e.what() << std::endl;
}
Key Components
- throw — Used to signal an exception.
- try — Wraps code that might throw an exception.
- catch — Handles the exception and defines recovery steps.
Types of Exceptions
- Standard Exceptions: Defined in
<stdexcept>and<exception>Examples:std::runtime_error,std::out_of_range,std::bad_alloc - User-defined Exceptions: Custom exception classes created for specific application needs.
- System Exceptions: Rare OS-level issues, often unrecoverable within C++.
Catching Multiple Exceptions
try {
// risky operations
}
catch (const std::invalid_argument &e) {
std::cout << "Invalid argument: " << e.what();
}
catch (const std::out_of_range &e) {
std::cout << "Out of range: " << e.what();
}
catch (...) {
std::cout << "Unknown exception caught.";
}
Exception Safety in C++
When writing exception-safe code, C++ developers aim for:
- No-throw guarantee: The function will never throw exceptions.
- Strong guarantee: Operations are either completed successfully or have no effect.
- Basic guarantee: Even if an exception occurs, resources are not leaked.
Nested try-catch Blocks
Nested handling allows different scopes to handle different exceptions:
try {
try {
throw std::logic_error("Inner error");
}
catch (const std::logic_error &e) {
std::cout << "Handled inner logic error\n";
throw; // rethrow to outer block
}
}
catch (const std::exception &e) {
std::cout << "Outer handler: " << e.what();
}
Best Practices
- Throw exceptions only for exceptional situations — not for normal control flow.
- Catch exceptions by
const referenceto avoid slicing and unnecessary copies. - Prefer specific exception types over
catch(...)to improve clarity. - Use
noexceptfor functions guaranteed not to throw exceptions.
When Exceptions Should Not Be Used
- Performance-critical loops where the overhead is too high.
- Very low-level code (e.g., embedded systems) where exception support may be disabled.
- When simple error codes are sufficient and clearer for the task.
Final Example
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 0);
}
catch (const std::invalid_argument &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}