Exception Handling
try, throw, and catch
basic-exception.cpp
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0.0) {
throw std::runtime_error("division by zero");
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << "\n"; // 5
std::cout << divide(5, 0) << "\n"; // throws before this prints
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Program keeps running.\n";
return 0;
}divide(5, 0) throws, the rest of the try block is skipped and control jumps straight to the matching catch block. The program does not crash — it recovers and keeps going.Standard exception types
<stdexcept> header provides a family of ready-made exception classes for common failure categories, so you rarely need to invent your own error type for ordinary cases.Type | Typical use |
|---|---|
| A general error only detectable at runtime (I/O failure, out-of-range computation). |
| A function received an argument that makes no sense (e.g. a negative size). |
| An index or key was outside the valid bounds of a container (e.g. |
| An error that in principle could have been avoided by the caller checking first. |
Always catch by reference
std::exception, which exposes a virtual what() method returning a human-readable description. That inheritance is exactly why catch parameters matter.catch (std::exception e), a thrown std::out_of_range object gets copied into a plain std::exception — the derived-class part is "sliced" off, and e.what() loses the specific message. Always catch by const reference: catch (const std::exception& e). It avoids the slicing, avoids an unnecessary copy, and still works polymorphically.Multiple catch blocks and catch-all
catch (...) catches literally anything, including exception types you didn't anticipate — useful as a last-resort safety net.multiple-catch.cpp
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> values = {1, 2, 3};
try {
std::cout << values.at(10) << "\n"; // throws std::out_of_range
} catch (const std::out_of_range& e) {
std::cout << "Index problem: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "Some other standard exception: " << e.what() << "\n";
} catch (...) {
std::cout << "Something totally unexpected was thrown.\n";
}
return 0;
}Writing a custom exception
std::exception (or a more specific standard type) and overriding what(). This lets callers catch your exception by name, or fall back to catching it generically as a std::exception.custom-exception.cpp
#include <exception>
#include <iostream>
#include <string>
class InsufficientFundsError : public std::exception {
public:
explicit InsufficientFundsError(double shortfall) : shortfall_(shortfall) {
message_ = "insufficient funds, short by " + std::to_string(shortfall_);
}
const char* what() const noexcept override { return message_.c_str(); }
private:
double shortfall_;
std::string message_;
};
void withdraw(double balance, double amount) {
if (amount > balance) {
throw InsufficientFundsError(amount - balance);
}
}
int main() {
try {
withdraw(100.0, 150.0);
} catch (const InsufficientFundsError& e) {
std::cout << "Withdrawal failed: " << e.what() << "\n";
}
return 0;
}noexcept on what() — it matches the base class signature and promises the method itself never throws, which is important since it's typically called while already handling another exception.RAII and exception safety
delete, closed a file, or released a lock at the bottom of its body, an exception thrown earlier would skip right past that cleanup — leaking memory or leaving a lock held forever.std::lock_guard, and file streams all rely on this: cleanup happens no matter how the function exits, including via an exception.Prefer throwing exception objects by value and catching by const reference.
Never let a destructor throw — an exception escaping a destructor during stack unwinding calls
std::terminate().Use RAII wrappers (smart pointers, lock guards, fstream objects) instead of manual acquire/release pairs so cleanup survives exceptions.
Catch exceptions only where you can meaningfully handle them — letting them propagate is often the right choice.