throw, try and catch
The function divide cannot divide by zero, but its return type obliges it to return some int. Say it returns -1 for a zero divisor:
#include <iostream>
int divide(int a, int b) {
if (b == 0) {
return -1;
}
return a / b;
}
int main() {
std::cout << divide(-7, 7) << ' ' << divide(1, 0) << '\n'; // -1 -1
}The caller cannot tell the error from a real quotient: -7 / 7 is also -1. Whatever number you pick, it may turn out to be a genuine result of a division.
Throwing an exception
Instead of a special value, the function can throw an exception—an object that reports an error bypassing return. A throw expression creates it. You can throw an int or a string too, but usually a separate class is declared for the error:
struct DivideByZero {};
int divide(int a, int b) {
if (b == 0) {
throw DivideByZero{};
}
return a / b;
}After throw, the function returns no value, and the line return a / b does not run. The exception leaves divide, then the function that called it, and so on—until it reaches code that is ready to handle it.
Catching an exception
Code that may throw an exception goes into a try block. It is followed by handlers—catch blocks with an exception type in parentheses:
int main() {
try {
std::cout << divide(7, 2) << '\n'; // 3
std::cout << divide(1, 0) << '\n';
std::cout << "done\n";
} catch (const DivideByZero&) {
std::cout << "division by zero\n"; // division by zero
}
}The second call throws, so the rest of the try block is skipped and done is not printed. The handler runs, and after it the code that follows the last catch. Exceptions are switched off in the course exercises: code with throw or try does not compile there.
On the way to a handler, the functions between throw and try end, and their local objects are destroyed in reverse order. This is stack unwinding: destructors of local objects run even when an exception leaves the function.
Order of handlers
Handlers are checked from top to bottom, and the first one that fits runs: unlike overloading, nothing looks for the best match. A handler fits if its parentheses name the exception's type or its public base class, by value or by reference. So a handler for a base class also catches exceptions of derived classes.
catch (...) catches an exception of any type and must come last, or the program does not compile. Inside a handler, throw; with no operand throws the same exception further, to an outer try. This way a handler can print a message and pass the error up. If no handler fits anywhere, std::terminate is called and the program ends; the stack is not guaranteed to unwind then.
Common mistake
A base-class handler comes before a derived-class handler:
struct InputError {};
struct EmptyInput : public InputError {};
void report() {
try {
throw EmptyInput{};
} catch (const InputError&) {
std::cout << "input error\n"; // input error
} catch (const EmptyInput&) { // ✗
std::cout << "empty input\n";
}
}warning: exception of type 'const EmptyInput &' will be caught by earlier handler [-Wexceptions]The program builds, but the second handler never runs: the first one catches EmptyInput. Write handlers for derived classes first, then for base classes.