Стандартные исключения
До сих пор мы бросали собственные мячи тревоги. Стандартная библиотека тоже бросает исключения, и на её мячах уже есть таблички с типом ошибки. Значит, сетку для них можно приготовить заранее.
Например, метод 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
}
}Программа печатает no element 5: у вектора три элемента, и v.at(5) бросает исключение, которое ловит обработчик.
В теме о контейнерах at для отсутствующего ключа останавливал программу. Так программа завершается при любом исключении, которое никто не поймал.
Иерархия std::exception
Классы стандартных исключений наследуют от std::exception из заголовка <exception>. У std::exception есть виртуальный метод what(): он возвращает текст описания ошибки. Тип у этого текста — const char*, указатель на первый символ, и std::cout печатает его как обычную строку.
Классы из списка ниже объявлены в заголовке <stdexcept>:
std::logic_error— ошибка в логике программы: нарушено условие, которое вызывающий код мог проверить заранее;std::invalid_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 создаёт из своего операнда отдельный объект — объект исключения. Он живёт по крайней мере до конца обработчика, который его поймал. Поэтому бросают сам объект, а не указатель на него: указатель на локальную переменную станет висячим при раскрутке стека.
Ловят исключение по константной ссылке: const std::exception& e обозначает сам объект исключения, без копии. Поймать можно и по значению, catch (std::exception e), но тогда e — новая копия типа из скобок. Для производного класса это срезка: копия — обычный 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';
}Компилятор не предупреждает, но бросили указатель типа std::runtime_error*, а обработчик ждёт ссылку на std::exception. Указатель ему не подходит, другого обработчика нет, и вызывается std::terminate. Бросайте сам объект: throw std::runtime_error("disk full");.
Главное
- Стандартная библиотека бросает исключения сама: например,
atбросаетstd::out_of_range. - Все стандартные исключения производны от
std::exception, аwhat()возвращает текст ошибки. - Свой класс исключения объявляют производным от
std::runtime_error. - Бросают сам объект, а ловят его по константной ссылке: так нет ни висячего указателя, ни срезки.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.