# Глава 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. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!