throw raises an exception of any type (though deriving from std::exception is conventional); try/catch handles it, with multiple catch blocks able to handle different exception types. The genuinely important mechanic is stack unwinding: as an exception propagates upward looking for a matching catch, every local object in each stack frame it passes through has its destructor called, in reverse order of construction — exactly as if each function had returned normally. This is precisely why RAII (covered in classes-and-raii) makes C++ exceptions safe: a smart pointer or container's destructor still runs during unwinding, releasing its resource correctly even though the function never reached its normal return statement.
throw, try, catch: the basics
Multiple catch blocks are checked in order, and the first matching type handles the exception — catch(...) at the end catches literally anything, useful as a last-resort fallback.
double divide(double a, double b) {
if (b == 0) {
throw std::invalid_argument("division by zero");
}
return a / b;
}
try {
double result = divide(10, 0);
} catch (const std::invalid_argument& e) {
std::cout << "Error: " << e.what() << "\n";
} catch (...) {
std::cout << "Unknown error\n"; // catches anything else
}Stack unwinding calls destructors automatically
Even though risky() throws before reaching its end, guard's destructor STILL runs during unwinding — the resource is released correctly, exactly as if the function had returned normally, purely because RAII ties cleanup to the object's lifetime rather than to reaching a specific line of code.
class ResourceGuard {
public:
ResourceGuard() { std::cout << "Acquiring\n"; }
~ResourceGuard() { std::cout << "Releasing\n"; } // runs during unwinding too
};
void risky() {
ResourceGuard guard; // acquired here
throw std::runtime_error("something went wrong");
// guard's destructor STILL runs, even though we never reach here
}
try {
risky();
} catch (const std::exception& e) {
std::cout << "Caught: " << e.what() << "\n";
}
// output: "Acquiring", then "Releasing", THEN "Caught: ..." — cleanup happened during unwindingnoexcept: promising a function won't throw
Marking a function noexcept lets the compiler apply certain optimizations, and communicates an intentional guarantee to callers — but if a noexcept function DOES throw anyway, the program calls std::terminate immediately rather than propagating the exception normally, which is a genuinely severe consequence worth knowing.
void safeCleanup() noexcept { // promises: this will never throw
// ...
}