Перейти к содержимому
Теория · Исключения · урок 3 из 7

Стандартные исключения

Стандартная библиотека тоже бросает исключения. Метод at вектора проверяет индекс и, если элемента нет, бросает исключение std::out_of_range:

C++
#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() возвращает именно его:

C++
int parse_digit(char c) {
    if (c < '0' || c > '9') {
        throw std::invalid_argument("not a digit");
    }
    return c - '0';
}

Обработчик std::exception ловит любое стандартное исключение, а виртуальный what() вызывается у того объекта, который бросили:

C++
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:

C++
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");.