Что такое try, throw и catch в C++
Язык C++предоставляет программистам встроенный инструмент для обработки ошибок: например, если зафиксирована попытка обратиться к несуществующей переменной, оборвалось интернет-соединение или не найден файл. Механизм называется «обработка исключений» (exception handling).
Исключение — это объект любого типа: строка, число, число с плавающей точкой и т. д. Он необходим для уведомления об ошибке. Задача исключения — передать сведения из точки ошибки тому, кто должен ее обработать. На примере ниже разберем, как это работает.
Синтаксис достаточно простой:
try
{
здесь код, который может выбросить исключение Throw
}
catch
{
обработка исключения
}
В некоторых случаях используют finally. Тогда блок кода будет выполнен в любом случае, вне зависимости от того, выпала ли ошибка. Finally обеспечивает выполнение критически важного кода: например, освобождение ресурсов, закрытие файлов, соединение с базой данных, даже если произошел сбой в коде.
Для примера возьмем ситуацию с работой в офисе.
int main() {
try {
идти на работу;
получать задачи;
писать код;
throw "Заболел!";
тестировать приложение;
созваниваться с менеджером
}
catch {
искать замену сотруднику
}
}
Допустим, есть сотрудник (блок с try). Идет стандартное выполнение кода = рабочих обязанностей: прийти в офис, получить задачи, начать писать код. Вдруг происходит ошибка (нештатная ситуация): заболел. Выполнение кода (задач) дальше не идет. Мы сразу попадаем в блок catch, где обрабатываем проброшенную ошибку: например, ищем замену сотруднику или откладываем задачи. В нашем случае мы тоже могли бы использовать finally — блок, который будет использован вне зависимости от того, была или не была ошибка. К примеру, «взять отгул».
Если в коде нет обработчиков ошибок, то при появлении проблемы программа просто прекращает выполнение. Сначала вызывается terminate() из <exception> стандартной библиотеки C++. Она вызывает следующую — abort() (из <cstdlib>). Эта функция и завершает программу.
Какие ошибки можно обрабатывать
В процессе разработки программисты могут столкнуться с разными типами ошибок. Их поиск и отлов которых («дебаггинг») занимает порой больше времени, чем написание кода.
Ошибки могут быть двух основных типов:
- аппаратные;
- программные.
Тип | Чем генерируются | Примеры, когда появляется |
Аппаратные (структурные, SE-Structured Exception) | Процессор | Деление на ноль, извлечение корня из отрицательного числа, выход за границы массива |
Программные | Прикладные программы или операционная система | При аномальной (нештатной) ситуации, которую обнаружила программа |
Как работают исключения в языке C++
Функция обнаруживает проблему и не знает, как ее решить (например, деление на 0). Она генерирует исключение throw в надежде, что прямо или косвенно вызывающая ее функция сможет устранить ошибку.
Для обработки используются:
- throw — объект для передачи сведений об ошибке. Выражение используется только в программных исключения и означает, что в блоке try что-то произошло. Исключение может «пробрасываться» выше — передавать обработку вызывающей функции;
- try — оператор, определяющий блок, может возникнуть исключение;
- catch — оператор, указывающий на тип отлавливаемого исключения и способ его обработки.
Простой код, в котором мы объявляем переменную и пытаемся разделить на 0:
#include <iostream>
int main() {
int den = 0;
int x = 50 / 0
}
Писать, тестировать и документировать код можно в отечественной среде разработки GigaIDE Desktop. В ней доступен умный ассистент GigaCode. В режиме code chat он объясняет код, делает ревью, предлагает улучшения и делает многое другое.
В данном случае закономерно появляется ошибка, о которой выше упомянул GigaCode.
Ниже попробуем устранить проблему и добавить хендлинг.
Примеры использования конструкции try-catch
Попытаемся изменить код и использовать обработку ошибок. В коде ниже появляется блок try, в котором выполняется код. Когда выбрасывается исключение throw, выполнение кода в try прекращается. Мы попадаем в блок catch и ловим то, что было выброшено в исключении. Дополнительно используем if для условия.
#include <iostream>
#include <string>
using namespace std;
double divide(int numerator, int denominator) {
if (denominator == 0) {
throw "Деление на ноль!";
}
return numerator / denominator;
}
int main() {
int num = 10;
int den = 0;
try {
double result = divide(num, den);
cout << "Результат деления: " << result << endl;
} catch (const char* error) {
cerr << "Ошибка: " << error << endl;
}
return 0;
}
Ситуации, когда используются обработчики, могут быть разными. Известны error при работе с:
- динамической памятью;
- базами данных;
- окнами, а также многие другие.
Ниже расскажем, какие инструменты предоставляет язык программирования C++.
Ловить исключения нескольких типов
Разработчик может предусмотреть возможность отловить исключения разных типов. Синтаксис:
catch(ExceptionType1 e) {
}
catch(ExceptionType2 e) {
}
Отловить все исключения
Подходит для ситуации, когда разработчик указал несколько блоков обработчиков в коде выше. Синтаксис catch(...) {} означает «поймать все остальные исключения».
Перебросить исключение
Если текущий обработки не может справиться с ошибкой, можно перебросить его другому обработчику:
catch(...) {throw}
Альтернативный вариант:
std::rethrow_exception(std::current_exception())
Но стоит учитывать, что current_exception() возвращает не объект исключения. Поэтому использовать его можно только со специальными функциями.
Использование throw для генерации исключений
В C++ исключения могут быть разных типов:
- встроенные типы данных;
- классы стандартных библиотек;
- пользовательские классы (созданы разработчиком).
Встроенные типы данных C++
Исключения можно пробрасывать в:
- int — целые числа;
- char, const char* — строки или символы;
- bool — логическое булево значение;
- float, double — числа с плавающей точкой.
В примере выше пробрасывали строку throw "Деление на ноль!". Но исключения могли выглядеть как throw 33 или throw true.
Классы стандартных библиотек
В стандартной библиотеке C++ предусмотрен базовый класс std::exception. Все исключения стандартной библиотеки наследуются от него. Всего их более 15 штук.
- logic_error — нарушение логики (деление на ноль, попытка извлечь корень из отрицательного числа). Его производные классы:
- invalid_argument — передан некорректный аргумент;
- length_error — лимит вместимости контейнера;
- out_of_range — значение за пределами допустимых границ.
- runtime_error — ошибка в процессе исполнения. Ее производными классами будут:
- regex_error — ошибка регулярных выражений (регулярок);
- format_error — ошибка форматирования (проблемы при работе std::format).
Существуют и другие: bad_typeid, ios_base::failure (начиная с C++11), bad_function_call (C++11), bad_alloc.
В реальности код может выглядеть сложнее. Например, если нужно обработать ошибку ответа сервера. В коде ниже объявленный класс ServerResponseException наследует стандартный класс std::exception для обработки ошибок.
#include <iostream>
#include <string>
class ServerResponseException : public std::exception {
private:
std::string message;
public:
explicit ServerResponseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
std::string getServerResponse() {
// Здесь обычно был бы реальный запрос к серверу
return "На сервере что-то пошло не так";
}
void processRequest() {
try {
std::string response = getServerResponse();
if (response.find("Ошибка") != std::string::npos) {
throw ServerResponseException(response);
}
std::cout << "Ответ сервера: " << response << std::endl;
} catch (const ServerResponseException& ex) {
std::cerr << "Ошибка сервера: " << ex.what() << std::endl;
}
}
int main() {
processRequest();
return 0;
}
Собственные пользовательские классы
На основе базового std::exception можно создавать кастомные исключения:
class MyCustomException : public std::exception {
private:
std::string message;
public:
explicit MyCustomException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
}.
Частые ошибки при обработке исключений
Среди основных ошибок, с которыми могут столкнуться разработчики:
- не добавили блок catch для хедлинга ошибок;
- добавили блок, но оставили часть исключений необработанными;
- использовали catch для проверки валидности или других задач в нормальном потоке программы (не для работы с критическими ситуациями).
Возможны и более сложные ошибки:
- использование set_terminate и set_unexpected (считается legacy-кодом);
- noexcept с указанием на то, что функция не выбрасывает исключения (а очень часто как раз она и становится причиной проблем в коде);
- передача исключений между потоками через аргументы функций или глобальные переменные.
Exception Handling — инструмент, который позволяет писать работающий, поддерживаемый и предсказуемый код. Важно правильно использовать его, а помочь в этом могут не только старшие коллеги, но и AI-ассистенты. Например, GigаCODE в режиме code chat не только объясняет код, но и делает ревью или предлагает улучшения. Чтобы запросить информацию, можно использовать хинты /review или /improve. Также доступны:
- /doc (сгенерировать документацию);
- /explain (объяснить код);
- /test (написать код);
- /explain_step_by_step (объяснить пошагово).