Стандартные исключения
Стандартная библиотека тоже бросает исключения. Метод at вектора проверяет индекс и, если элемента нет, бросает исключение std::out_of_range:
#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
}
}В теме о контейнерах at для отсутствующего ключа останавливал программу. Так заканчивается любое исключение, которое никто не поймал.
Иерархия std::exception
Классы стандартных исключений наследуют от std::exception из заголовка (header) <exception>. Его виртуальный метод what() возвращает описание ошибки — строку типа const char*. Классы из списка ниже объявлены в заголовке <stdexcept>:
std::logic_error— ошибка в логике программы: нарушено условие, которое вызывающий код мог проверить заранее;std::invalid_argument— недопустимый аргумент (argument), производный отstd::logic_error;std::out_of_range— индекс или ключ вне допустимых границ, тоже производный отstd::logic_error;std::runtime_error— ошибка, которую можно обнаружить только во время работы программы.
Конструктор каждого из этих классов принимает текст сообщения, и what() возвращает именно его:
int parse_digit(char c) {
if (c < '0' || c > '9') {
throw std::invalid_argument("not a digit");
}
return c - '0';
}Обработчик std::exception ловит любое стандартное исключение, а виртуальный what() вызывается у того объекта, который бросили:
try {
std::cout << parse_digit('x') << '\n';
} catch (const std::exception& e) {
std::cout << e.what() << '\n'; // not a digit
}Свой класс исключения тоже объявляют производным от одного из этих классов, обычно от std::runtime_error. Тогда его поймает и обработчик std::exception, а what() вернёт текст, переданный конструктору std::runtime_error.
Бросать значение, ловить ссылку
Выражение throw создаёт из своего операнда отдельный объект — объект исключения (exception object). Он живёт по крайней мере до конца обработчика, который его поймал. Поэтому бросают сам объект, а не указатель (pointer) на него: указатель на локальную переменную станет висячим (dangling) при раскрутке стека.
Ловят исключение по константной ссылке: const std::exception& e обозначает сам объект исключения, без копии. Поймать можно и по значению, catch (std::exception e), но тогда e — новая копия типа из скобок. Для производного класса это срезка (object slicing): копия — обычный std::exception, и вызов what() у неё выполняет std::exception::what, а не версию брошенного класса.
Частая ошибка
Исключение создают через new:
try {
throw new std::runtime_error("disk full"); // ✗
} catch (const std::exception& e) {
std::cout << e.what() << '\n';
}Компилятор (compiler) не предупреждает, но бросили указатель типа std::runtime_error*, а обработчик ждёт ссылку на std::exception. Указатель ему не подходит, другого обработчика нет, и вызывается std::terminate. Бросайте сам объект: throw std::runtime_error("disk full");.