Introduction
In C, a function reports an error with a special return value (-1, NULL) that every caller must check. C++ adds exceptions: a function that cannot do its job throws an exception, and the nearest matching catch handles it, however many functions up the call stack it is.
Return codes are easy to ignore and clutter every caller. Exceptions separate the code that finds an error from the code that deals with it, and C++ makes sure everything in between is cleaned up properly.
How it works
throw, try and catch
#include <iostream>
#include <stdexcept>
using namespace std;
double divide(double a, double b) {
if (b == 0) throw runtime_error("division by zero");
return a / b;
}
int main() {
try {
cout << divide(10, 4) << endl;
cout << divide(1, 0) << endl;
cout << "not printed" << endl;
} catch (const exception& e) {
cout << "error: " << e.what() << endl;
}
cout << "the program goes on" << endl;
return 0;
}throw runtime_error("division by zero")creates an exception object and leavesdivideat once: thereturnafter it never runs.- The call sits inside
try { ... }, so C++ looks for acatchthat matches the exception's type.catch (const exception& e)matches any standard exception, becauseruntime_erroris derived fromexception.e.what()gives its message. - After the
catchblock, the program carries on normally. Watch the trace: the throw, the jump to the catch, and the exception object on the heap.
Catch by const &: it does not copy the exception, and it keeps the derived type for what().
Syntax
try {
riskyWork();
} catch (const out_of_range& e) { // the most specific first
cout << e.what() << endl;
} catch (const exception& e) { // anything else
cout << "error: " << e.what() << endl;
}
Example
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class Guard {
string name;
public:
Guard(string n) : name(n) {
cout << "enter " << name << endl;
}
~Guard() {
cout << "leave " << name << endl;
}
};
void inner() {
Guard g("inner");
throw runtime_error("something failed");
}
void outer() {
Guard g("outer");
inner();
cout << "not printed" << endl;
}
int main() {
try {
outer();
} catch (const runtime_error& e) {
cout << "caught: " << e.what() << endl;
}
return 0;
}When an exception leaves a function, its local objects are still destroyed properly: their destructors run, newest first. Here inner's Guard and outer's Guard both say goodbye before the catch runs. This stack unwinding is why RAII objects (vector, string, your own classes with destructors) never leak, even when errors happen.
| Exception class | Thrown by / used for |
|---|---|
runtime_error | errors found while running |
out_of_range | vector::at, string::at with a bad index |
invalid_argument | stoi("abc") |
exception | the base class of all of them: catch it last |
Line by line
Guardprintsenterwhen it is built andleavewhen it is destroyed.innermakes a guard and then throws aruntime_error.outermakes its own guard and callsinner. The line after the call never runs.- As the exception leaves
inner, its guard is destroyed, and the same happens as it leavesouter. - The
catchinmainmatchesruntime_errorand prints the message, after bothleavelines.
Expected result
enter outer
enter inner
leave inner
leave outer
caught: something failed
In the real world
Standard library functions such as vector::at and stoi report errors by throwing. Large applications usually catch exceptions near the top of each task, such as a web request, so that one failed task does not stop the whole program.
Common mistakes
- An exception that nothing catches stops the program (terminate). StepVioz tells you which type was thrown.
- Catching by value (
catch (exception e)): the copy is sliced, andwhat()may say only "std::exception". - Putting
catch (exception&)beforecatch (out_of_range&): the first matching catch wins, so list the specific ones first.
Best practices
- Throw objects of standard exception types, or of classes derived from them.
- Catch by
const&. - Use exceptions for real errors, not for ordinary control flow.
Summary
A function throws an exception when it cannot do its job, and the nearest matching catch handles it. Stack unwinding destroys local objects on the way, so destructors still clean up, and catch blocks are tried in order.
Quick check
Key takeaways
- An exception jumps to the nearest matching catch.
- Local objects are destroyed on the way, so RAII cleans up.
- Catch by const reference, with the most specific type first.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.