# Глава 5.2. Исключения
Концептуально есть два подхода к обработке ошибок. Первый заключается в том, что функция так или иначе возвращает наружу некий признак. _По месту вызова_ функции этот признак проверяется. Затем либо обрабатывается ошибочная ситуация, либо продолжается штатное выполнение кода. Если эта же функция вызывается пятью строками ниже — что ж, все те же проверки должны быть и там.
Второй подход основывается на разделении кода для обработки ошибок и кода, реализующего основную логику программы. К этому подходу относится и механизм исключений.
## Как работают исключения
В случае ошибки:
1. Функция создаёт специальный объект — исключение (exception). Его цель — передать информацию об ошибке из точки обнаружения в точку обработки.
2. Функция прерывает своё выполнение — бросает исключение.
3. Управление передаётся обработчику исключения. Он находится в коде, вызвавшем функцию явно _или косвенно._ Если подходящий обработчик не найден, программа завершает работу.
У каждого из подходов есть свои достоинства и недостатки. В проекте «Деление без деления», который вы только что выполнили, реализован первый подход. В нем деление на ноль обрабатывается через возврат специального значения:
```cpp
inline std::size_t divide(std::size_t a, std::size_t b)
{
if (b == 0)
return std::numeric_limits<std::size_t>::max();
// ...
}
```
Во всех местах вызова `divide()` нужно не забыть проверку: вдруг вернулось значение, говорящее об ошибке?
```cpp
const std::size_t res = divide(blob_len, chunk_len);
if (res == std::numeric_limits<std::size_t>::max())
std::println("Error in divide()");
```
## Оператор throw
Давайте перепишем этот код. Чтобы избежать деления на ноль, бросим исключение [оператором throw:](https://en.cppreference.com/w/cpp/language/throw)
```cpp
inline std::size_t divide(std::size_t a, std::size_t b)
{
if (b == 0)
throw std::runtime_error("division by zero");
// ...
}
```
На вершине [иерархии исключений](https://en.cppreference.com/w/cpp/error/exception) стандартной библиотеки находится класс `std::exception`. Это наиболее общее исключение, от которого наследуются все остальные. Использованный в примере `std::runtime_error` также является его потомком.
## Конструкция try-catch
Выполнение кода, бросившего исключение, прерывается. Управление передаётся обработчику исключения. Для этого бросивший исключение код должен _явно или косвенно_ вызываться из блока `try` [конструкции try-catch.](https://en.cppreference.com/w/cpp/language/catch)
Блок `catch` и является обработчиком. Он перехватывает и обрабатывает исключения указанного класса и его потомков.
```cpp
try
{
const std::size_t res = divide(blob_len, chunk_len);
// ...
}
catch(const std::runtime_error & e)
{
std::println("Error {}", e.what());
return;
}
```
В круглых скобках блока `catch` указывается объект исключения. Между именем объекта и его классом ставится символ амперсанда `&`. Это означает, что объект передаётся по ссылке, а не по значению. В следующих главах вы узнаете, что такое ссылки. А пока просто не забывайте ставить `&` перед именем исключения в блоке `catch`.
В ряде случаев нет необходимости ловить конкретный тип исключения. Можно ловить сразу группу исключений, используя общий родительский класс. В нашем примере мы могли заменить `std::runtime_error` в блоке `catch` на `std::exception`, чтобы ловить _все_ стандартные исключения и их наследников.
У каждого из классов исключений есть метод `what()` для получения строковой информации об ошибке.
Функция `handle_daily_stats()` обрабатывает статистику действий пользователей на сайте. Функции подготовки статистики, которые она вызывает, в случае неудачи возвращают `false`. {.task_text}
Было принято решение отказаться от обработки возвращаемого из функций флага в пользу обработки исключений. Теперь функции `filter()`, `sort()` и `count()` ничего не возвращают, а в случае ошибки бросают `std::runtime_error` с соответствующим текстом. {.task_text}
Требуется переписать код таким образом, чтобы исключение обрабатывалось в функции `handle_daily_stats()`: в консоль должна выводиться ошибка, возвращаемая методом `what()`. {.task_text}
```cpp {.task_source #cpp_chapter_0052_task_0020}
void handle_daily_stats()
{
if (!filter())
{
std::println("filter error");
return;
}
if (!sort())
{
std::println("sort error");
return;
}
if (!count())
{
std::println("count error");
return;
}
std::println("success");
}
```
В функцию `handle_daily_stats()` добавьте конструкцию `try-catch`. В блоке `try` последовательно вызовите функции `filter()`, `sort()`, `count()`, а также выведите в консоль строку `"success"`. В блоке `catch` перехватите исключение `const std::runtime_error & e` и выведите в консоль строку, возвращаемую методом `e.what()`. {.task_hint}
```cpp {.task_answer}
void handle_daily_stats()
{
try
{
filter();
sort();
count();
std::println("success");
}
catch(const std::runtime_error & e)
{
std::println("{}", e.what());
}
}
```
----------
## Резюме
- Оператор `throw` и конструкция `try-catch` реализуют механизм исключений.
- Исключения позволяют:
- Разграничивать логику программы и логику обработки ошибок.
- Выбирать, в каком месте кода обрабатывать ошибки какого типа.
- Группировать обработку ошибок по типам.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!