20.x – Резюме к главе 20 и небольшой тест
Обработка исключений предоставляет механизм, позволяющий отделить обработку ошибок или других исключительных обстоятельств от обычного управления порядком выполнения вашего кода. Это дает больше свободы для обработки ошибок, когда и как ее лучше выполнить в какой-либо конкретной ситуации, что уменьшает многое (если не полностью устраняет) в беспорядке, вызываемом кодами возврата.
Для создания исключения используется инструкция throw
. Блоки try
ищут исключения, вызванные написанным или вызываемым в них кодом. Эти исключения направляются в блоки catch
, которые перехватывают исключения определенных типов (при их совпадении) и обрабатывают их. По умолчанию перехваченное исключение считается обработанным.
Исключения обрабатываются немедленно. Если возникает исключение, управление переходит к ближайшему охватывающему блоку try
в поисках обработчиков catch
, которые могут обработать это исключение. Если блок try
не найден, или нет соответствующего блока catch
, стек будет разворачиваться до тех пор, пока не будет найден обработчик. Если обработчик не будет найден до того, как весь стек будет раскручен, программа завершится с ошибкой необработанного исключения.
Исключения могут быть любого типа данных, включая классы.
Блоки catch
могут быть сконфигурированы для перехвата исключений определенного типа данных, или с помощью многоточия (...
) обработчик catch
может быть настроен для перехвата исключений всех типов данных. Блок catch
, перехватывающий ссылку на базовый класс, также перехватывает исключения производного класса. Все исключения, создаваемые стандартной библиотекой, являются производными от класса std::exception
(который находится в заголовке exception
), поэтому перехват std::exception
по ссылке перехватит все исключения стандартной библиотеки. Чтобы определить, какое именно исключение std::exception
было сгенерировано, можно воспользоваться функцией-членом what()
.
Внутри блока catch
может быть создано новое исключение. Поскольку это новое исключение генерируется за пределами блока try
, связанного с этим блоком catch
, оно не будет перехвачено блоком catch
, в котором оно было выброшено. Исключения в блоке catch
можно повторно выбрасывать, используя только ключевое слово throw
. Не выбрасывайте повторно исключение, используя перехваченную переменную исключения, так как в этом случае может произойти обрезка объекта.
Функциональные блоки try
дают вам возможность перехватить любое исключение, возникающее в функции или в связанном с ней списке инициализации членов. Обычно они используются только с конструкторами производных классов.
Вы никогда не должны выбрасывать исключение в деструкторах.
Для обозначения того, что функция не вызывает исключений/сбоев может использоваться спецификатор исключения noexcept
.
Наконец, обработка исключений имеет свою цену. В большинстве случаев код, использующий исключения, будет работать немного медленнее, а затраты на обработку исключения очень высоки. Вы должны использовать исключения только для обработки исключительных обстоятельств, а не для обычных случаев обработки ошибок (например, недопустимый ввод).
Небольшой тест
Вопрос 1
Напишите класс дроби Fraction
с конструктором, который принимает числитель и знаменатель. Если пользователь передает знаменатель 0, генерируется исключение типа std::runtime_error
(включено в заголовок stdexcept
). В функции main()
попросите пользователя ввести два целых числа. Если дробь будет корректна, выведите ее. Если дробь некорректна, перехватите std::exception
и сообщите пользователю, что он ввел недопустимую дробь.
Вот что должна вывести программа при выполнении:
Enter the numerator: 5
Enter the denominator: 0
Invalid denominator
Ответ
#include <iostream> #include <stdexcept> // для std::runtime_error class Fraction { private: int m_numerator = 0; int m_denominator = 1; public: Fraction(int numerator = 0, int denominator = 1) : m_numerator(numerator), m_denominator(denominator) { if (m_denominator == 0) throw std::runtime_error("Invalid denominator"); } friend std::ostream& operator<<(std::ostream &out, const Fraction &f1); }; std::ostream& operator<<(std::ostream &out, const Fraction &f1) { out << f1.m_numerator << "/" << f1.m_denominator; return out; } int main() { std::cout << "Enter the numerator: "; int numerator; std::cin >> numerator; std::cout << "Enter the denominator: "; int denominator; std::cin >> denominator; try { Fraction f(numerator, denominator); std::cout << "Your fraction is: " << f << '\n'; } catch (std::exception& e) { std::cout << e.what() << '\n'; } return 0; }