Exceptions
When a function goes wrong, how does it tell whoever called it? The old way is to return a special value, like "return 0 if it can't be worked out". But what if 0 is also a normal answer?
1#include <iostream>2#include <vector>3 4double average(const std::vector<double>& temps) {5 if (temps.empty()) {6 return 0;7 }8 double total = 0;9 for (double t : temps) {10 total += t;11 }12 return total / temps.size();13}14 15int main() {16 std::vector<double> winter{-3, 1, 2};17 std::vector<double> nothing;18 std::cout << "Winter average: " << average(winter) << '\n';19 std::cout << "No data: " << average(nothing) << '\n';20 return 0;21}
Winter average: 0 No data: 0
Two zeros: one is a real average temperature, the other means "there's no data at all", and the caller can't tell them apart. Even if it could, it would have to remember to check after every call. C++ has another way, exceptions: when something goes wrong, the function doesn't return; it "throws" an exception, handing the problem to someone who knows what to do.
throw and try / catch
1#include <iostream>2#include <stdexcept>3#include <vector>4 5double average(const std::vector<double>& temps) {6 if (temps.empty()) {7 throw std::runtime_error("no data");8 }9 double total = 0;10 for (double t : temps) {11 total += t;12 }13 return total / temps.size();14}15 16int main() {17 std::vector<double> nothing;18 try {19 double avg = average(nothing);20 std::cout << "Average: " << avg << '\n';21 } catch (const std::runtime_error& e) {22 std::cout << "Something went wrong: " << e.what() << '\n';23 }24 std::cout << "The program goes on\n";25 return 0;26}
Something went wrong: no data The program goes on
Three new things:
throw std::runtime_error("no data");creates an exception object and throws it.averagestops right there and returns no value.std::runtime_erroris in<stdexcept>; the text in parentheses says what went wrong.try { … }surrounds code that might go wrong.catch (const std::runtime_error& e) { … }follows thetryand catches that kind of exception.e.what()gives back the text written when it was thrown.
After the throw, the rest of the try is skipped and control goes straight to the catch; once the catch is done, the program carries on after the whole try / catch. So the "Average: " line doesn't show, and "The program goes on" does.
When nobody catches it
If nobody catches it, the exception keeps going outward, out of main, and the program ends:
1#include <iostream>2#include <stdexcept>3#include <vector>4 5double average(const std::vector<double>& temps) {6 if (temps.empty()) {7 throw std::runtime_error("no data");8 }9 double total = 0;10 for (double t : temps) {11 total += t;12 }13 return total / temps.size();14}15 16int main() {17 std::vector<double> nothing;18 double avg = average(nothing);19 std::cout << "Average: " << avg << '\n';20 std::cout << "End\n";21 return 0;22}
terminate called after throwing an instance of 'std::runtime_error'
what(): no data
Aborted (core dumped)What happened: An exception (std::runtime_error) was thrown and no catch took it, so the program ended (std::terminate).
These three lines are what a g++ program shows on your own computer too: terminate called after throwing an instance of 'std::runtime_error' says a std::runtime_error was thrown and nobody caught it, so std::terminate was called; after what(): comes the text; Aborted (core dumped) means the program was forced to stop. "End" never shows.
An exception can't be ignored: either it's caught, or the program ends. That beats returning a 0 that anyone might forget to check.
The standard library throws too
Lesson 11 said v.at(5) out of range throws std::out_of_range, and the program ends if nothing handles it. Lesson 5's std::stoi throws std::invalid_argument when the text isn't a number. Now you can catch them:
1#include <iostream>2#include <stdexcept>3#include <string>4#include <vector>5 6int main() {7 std::vector<int> prices{12, 30, 8};8 try {9 std::cout << prices.at(5) << '\n';10 } catch (const std::exception& e) {11 std::cout << "Bad index: " << e.what() << '\n';12 }13 try {14 int n = std::stoi("twelve");15 std::cout << n << '\n';16 } catch (const std::exception& e) {17 std::cout << "Not a number: " << e.what() << '\n';18 }19 std::cout << "Both errors caught\n";20 return 0;21}
Bad index: vector::_M_range_check: __n (which is 5) >= this->size() (which is 3) Not a number: stoi Both errors caught
This catches const std::exception&. std::exception is the base class of every standard exception (inheritance, lesson 23): std::runtime_error, std::out_of_range and std::invalid_argument are all derived from it, so one catch (const std::exception& e) catches them all, and e.what() calls each one's own version (virtual functions, lesson 24). The what() from stoi is short, just stoi.
Catch by reference (&): no copy, and a derived object isn't sliced down to the base. Add const, since you're only looking. That's the usual rule for catching.
Several catches
A try can be followed by several catches, each doing something different for a different error. When an exception is thrown, they're checked top to bottom, and the first one that matches catches it; the rest are ignored:
1#include <iostream>2#include <stdexcept>3#include <string>4#include <vector>5 6int seat_number(const std::string& text) {7 int n = std::stoi(text);8 if (n < 1 || n > 30) {9 throw std::runtime_error("no such seat");10 }11 return n;12}13 14int main() {15 std::vector<std::string> tickets{"12", "abc", "45"};16 for (const auto& t : tickets) {17 try {18 int seat = seat_number(t);19 std::cout << "Seat " << seat << '\n';20 } catch (const std::invalid_argument& e) {21 std::cout << t << " is not a number\n";22 } catch (const std::runtime_error& e) {23 std::cout << t << ": " << e.what() << '\n';24 }25 }26 return 0;27}
Seat 12 abc is not a number 45: no such seat
"abc" makes stoi throw invalid_argument, caught by the first catch; "45" makes seat_number itself throw runtime_error ("no such seat" is this program's own rule, so it uses runtime_error), which the first doesn't match, so the second catches it. The try is inside the loop body, so one bad ticket doesn't stop the next.
Since the first match wins, put specific catches first and catch-everything ones like std::exception last. There's also catch (...), with three dots, which catches any type but gives you no what(); it can only be the last one.
What does this program show?
1#include <iostream>2#include <stdexcept>3 4void check(int n) {5 std::cout << "a";6 if (n > 2) {7 throw std::runtime_error("too big");8 }9 std::cout << "b";10}11 12int main() {13 try {14 check(1);15 check(5);16 check(2);17 std::cout << "c";18 } catch (const std::exception& e) {19 std::cout << "d";20 }21 std::cout << "e\n";22 return 0;23}
check(1) shows ab; check(5) shows a and then throws, so no b; the rest of the try, check(2) and c, is skipped, and the catch shows d; then the program carries on and shows e.
Objects on the way are destroyed
An exception jumps from throw to catch, maybe across several layers of functions. What about the local objects in those layers? C++ guarantees that as each layer is left, its local objects are destroyed, just as when a function ends normally. This is called stack unwinding.
1#include <iostream>2#include <stdexcept>3#include <string>4 5class Cup {6public:7 Cup(const std::string& name) : name_(name) {8 std::cout << "Take out the " << name_ << '\n';9 }10 ~Cup() {11 std::cout << "Put away the " << name_ << '\n';12 }13 14private:15 std::string name_;16};17 18void make_tea() {19 Cup cup{"teacup"};20 throw std::runtime_error("not enough water");21 std::cout << "Pour the tea\n";22}23 24int main() {25 try {26 Cup tray{"tray"};27 make_tea();28 } catch (const std::exception& e) {29 std::cout << "Caught: " << e.what() << '\n';30 }31 return 0;32}
Take out the tray Take out the teacup Put away the teacup Put away the tray Caught: not enough water
Press Step, and after the throw on line 20, go one step at a time: the next step isn't the catch but the destructor of cup in make_tea; then the destructor of tray in main's try; only then the catch. The teacup is put away before the tray, the reverse of the order they were made.
That's why lesson 21's smart pointers and lesson 11's vector don't leak when an exception passes through: they're local objects, and their destructors still run.
Your own exception class
The standard exception classes are often enough, but sometimes you want the exception to carry more, or want a catch to tell "this is a bank error". Write a class that inherits from std::runtime_error:
1#include <iostream>2#include <stdexcept>3#include <string>4 5class BankError : public std::runtime_error {6public:7 BankError(const std::string& message, int short_by)8 : std::runtime_error(message), short_by_(short_by) {}9 int short_by() const {10 return short_by_;11 }12 13private:14 int short_by_;15};16 17class Account {18public:19 Account(int balance) : balance_(balance) {}20 void withdraw(int amount) {21 if (amount > balance_) {22 throw BankError("not enough money", amount - balance_);23 }24 balance_ -= amount;25 std::cout << "Took out " << amount << ", " << balance_ << " left\n";26 }27 28private:29 int balance_;30};31 32int main() {33 Account account{100};34 try {35 account.withdraw(30);36 account.withdraw(100);37 } catch (const BankError& e) {38 std::cout << e.what() << ", short by " << e.short_by() << '\n';39 } catch (const std::exception& e) {40 std::cout << "Other error: " << e.what() << '\n';41 }42 return 0;43}
Took out 30, 70 left not enough money, short by 30
BankError's constructor calls the base class's constructor std::runtime_error(message) in its initializer list (lesson 23), and that gives it what(); it adds its own member short_by_ for how much is missing. Because it's derived from std::runtime_error, code elsewhere that only has catch (const std::exception& e) catches it too.
Note: throw can actually be followed by any value: throw 42; or throw std::string("oops"); both work. But then there's no what(), and catch (const std::exception&) won't catch it. By habit, throw only standard exceptions or classes derived from them.
When to use exceptions
Exceptions suit errors that "can't be handled here and shouldn't happen": the function can't do what it promised and doesn't know what to do, so it passes the problem up. Constructors are the classic place. A constructor has no return value, so when its arguments are wrong, throwing is the only way out, and that way there's never a half-broken object:
1#include <iostream>2#include <stdexcept>3 4class Discount {5public:6 Discount(int percent) : percent_(percent) {7 if (percent < 0 || percent > 100) {8 throw std::invalid_argument("discount must be from 0 to 100");9 }10 }11 double apply(double price) const {12 return price * (100 - percent_) / 100;13 }14 15private:16 int percent_;17};18 19int main() {20 try {21 Discount sale{20};22 std::cout << sale.apply(50) << '\n';23 Discount wrong{120};24 std::cout << wrong.apply(50) << '\n';25 } catch (const std::invalid_argument& e) {26 std::cout << e.what() << '\n';27 }28 return 0;29}
40 discount must be from 0 to 100
wrong is never created, so nothing gets the chance to use a bad discount.
| Situation | Use |
|---|---|
| Common, and the caller deals with it at once (not found, bad input) | a return value, like find returning end() |
| Rare, and can't be handled here (bad arguments, a resource ran out) | throw an exception |
| A bug in the program (an index worked out wrong) | fix the code; don't hide it with catch |
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it. Checking compares only the output, so in these exercises the catch shows a line, proving it really caught something.
Catch the bad index
✓The second order uses index 7, so menu.at(7) throws and the program ends. Add a try / catch in the loop body that catches std::out_of_range and shows No index 7. The program should show these two lines.
Tea No index 7
1#include <iostream>2#include <stdexcept>3#include <string>4#include <vector>5 6int main() {7 std::vector<std::string> menu{"Coffee", "Tea", "Juice"};8 std::vector<int> picks{1, 7};9 for (int i : picks) {10 std::cout << menu.at(i) << '\n';11 }12 return 0;13}
No dividing by zero
✓main is ready to catch, but divide doesn't throw on 0. Make divide throw a std::runtime_error with the text can't divide by 0 when b is 0. The program should show these two lines.
5 Error: can't divide by 0
1#include <iostream>2#include <stdexcept>3#include <vector>4 5int divide(int a, int b) {6 return a / b;7}8 9int main() {10 std::vector<int> divisors{2, 0};11 for (int b : divisors) {12 try {13 std::cout << divide(10, b) << '\n';14 } catch (const std::runtime_error& e) {15 std::cout << "Error: " << e.what() << '\n';16 }17 }18 return 0;19}
Fix it: the specific catch goes first
✓"eight" isn't a number, so this should show Not a number: eight, but it shows Something went wrong, and the compiler gives a warning. Only change the order of the code, so the program shows these two lines.
16 Not a number: eight
1#include <iostream>2#include <stdexcept>3#include <string>4#include <vector>5 6int main() {7 std::vector<std::string> inputs{"8", "eight"};8 for (const auto& text : inputs) {9 try {10 int n = std::stoi(text);11 std::cout << n * 2 << '\n';12 } catch (const std::exception& e) {13 std::cout << "Something went wrong\n";14 } catch (const std::invalid_argument& e) {15 std::cout << "Not a number: " << text << '\n';16 }17 }18 return 0;19}
What you learned
- When something goes wrong, a function can
throwan exception, likethrow std::runtime_error("no data");(<stdexcept>), instead of returning a special value that's easy to mix up. try { … }surrounds code that might fail,catch (const std::runtime_error& e)catches it, ande.what()is the text; after the catch, the program carries on after thetry/catch.- If nobody catches it, the program ends:
terminate called after throwing an instance of …,what():,Aborted (core dumped). v.at()andstd::stoithrow on their own (out_of_range,invalid_argument), and now you can catch them.catch (const std::exception& e)catches every standard exception; with severalcatches the first match wins, so specific ones go first andcatch (...)goes last.- After a throw, the local objects in every layer between
throwandcatchare destroyed (stack unwinding). - Your own exception class inherits from
std::runtime_error, with a constructor that says: std::runtime_error(message). - Use exceptions for rare errors that can't be handled where they happen; use return values for common cases; fix bugs in the code.