Standard exceptions
The standard library throws exceptions too. The at method of a vector checks the index and, if there is no such element, throws a std::out_of_range exception:
#include <iostream>
#include <stdexcept>
#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 << "no element 5\n"; // no element 5
}
}In the containers theme, at stopped the program for a missing key. Any exception that nobody catches ends the same way.
The std::exception hierarchy
The standard exception classes derive from std::exception in the <exception> header. Its virtual method what() returns a description of the error as a const char* string. The classes below are declared in the <stdexcept> header:
std::logic_error—an error in the program's logic: a condition the caller could have checked in advance is broken;std::invalid_argument—an invalid argument, derived fromstd::logic_error;std::out_of_range—an index or a key outside the valid bounds, also derived fromstd::logic_error;std::runtime_error—an error that can be detected only while the program runs.
The constructor of each of these classes takes a message text, and what() returns exactly that text:
int parse_digit(char c) {
if (c < '0' || c > '9') {
throw std::invalid_argument("not a digit");
}
return c - '0';
}A handler for std::exception catches any standard exception, and the virtual what() is called on the object that was thrown:
try {
std::cout << parse_digit('x') << '\n';
} catch (const std::exception& e) {
std::cout << e.what() << '\n'; // not a digit
}Your own exception class is also declared as a derived class of one of these, usually std::runtime_error. Then a handler for std::exception catches it too, and what() returns the text passed to the std::runtime_error constructor.
Throw values, catch references
A throw expression creates a separate object from its operand—the exception object. It lives at least until the handler that caught it finishes. So you throw the object itself, not a pointer to it: a pointer to a local variable dangles after stack unwinding.
An exception is caught by const reference: const std::exception& e denotes the exception object itself, with no copy. It can also be caught by value, catch (std::exception e), but then e is a new copy of the type in parentheses. For a derived class this is object slicing: the copy is a plain std::exception. Calling what() on it runs std::exception::what, not the thrown class's version.
Common mistake
The exception is created with new:
try {
throw new std::runtime_error("disk full"); // ✗
} catch (const std::exception& e) {
std::cout << e.what() << '\n';
}The compiler does not warn, but the thrown value is a std::runtime_error* pointer, while the handler expects a reference to std::exception. The pointer does not fit it, there is no other handler, and std::terminate is called. Throw the object itself: throw std::runtime_error("disk full");.