C++ Exceptions
Errors happen: a file is missing, a number is divided by zero, the user types text instead of a number. Exceptions let the code that finds a problem report it, and the code that can handle it decide what to do, even several function calls higher up.
throw, try and catch
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) throw std::invalid_argument("cannot divide by zero");
return a / b;
}
int main() {
try {
std::cout << divide(10, 4) << "\n";
std::cout << divide(1, 0) << "\n"; // throws: the rest of the try block is skipped
std::cout << "not printed\n";
} catch (const std::invalid_argument& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "The program goes on.\n";
return 0;
}2.5 Error: cannot divide by zero The program goes on.
throw stops the current function at once, and every function between it and the matching catch. Catch exceptions by const&; what() returns the message.
Exceptions from the standard library
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
try {
std::cout << v.at(5) << "\n";
} catch (const std::out_of_range&) {
std::cout << "index outside the vector\n";
}
try {
int n = std::stoi("forty");
std::cout << n << "\n";
} catch (const std::invalid_argument&) {
std::cout << "not a number\n";
}
return 0;
}index outside the vector not a number
The standard exceptions form a family: out_of_range and invalid_argument are kinds of logic_error, which is a kind of exception. catch (const std::exception& e) catches them all, so put specific catch blocks first and the general one last. catch (...) catches anything.
Clean-up happens automatically
While an exception travels up, the destructors of all local objects in between run, so files close and memory is freed. This is why C++ code uses objects like std::vector, std::string, file streams and smart pointers instead of raw resources: they clean up even when an exception is thrown.
Your own exceptions
Derive from a standard exception class to add your own kinds of errors:
#include <iostream>
#include <stdexcept>
#include <string>
class InsufficientFunds : public std::runtime_error {
public:
explicit InsufficientFunds(double needed)
: std::runtime_error("not enough money: " + std::to_string(static_cast<int>(needed)) + " more needed") {}
};
void withdraw(double& balance, double amount) {
if (amount > balance) throw InsufficientFunds(amount - balance);
balance -= amount;
}
int main() {
double balance = 500;
try {
withdraw(balance, 200);
withdraw(balance, 400);
} catch (const InsufficientFunds& e) {
std::cout << e.what() << "\n";
}
std::cout << "balance " << balance << "\n";
return 0;
}not enough money: 100 more needed balance 300
Uncaught exceptions
If no catch matches, the program stops. In our editor the terminal shows the same message a g++ program prints, terminate called after throwing an instance of … with the what() text, and the exit code 134.
When to use exceptions
Use them for real errors that the current function cannot handle: a missing file, invalid data, a broken connection. Do not use them for normal results such as "not found"; return a value (or std::optional) for those.
Practice exercises
- Write a function that reads an age and throws
std::out_of_rangewhen it is below 0 or above 150, and catch it inmain. - Keep asking the user for a number with
getlineandstoiuntil the conversion works. - Remove the
tryaroundv.at(5)and read what the terminal prints.