Включите исполнение JavaScript в браузере, чтобы запустить приложение.

Операторы try, throw и catch (C++)

Что такое try, throw и catch в C++? Разбираем на реальных примерах, как выбрасываются и обрабатываются исключения в языке программирования.

Что такое try, throw и catch в C++

Язык C++предоставляет программистам встроенный инструмент для обработки ошибок: например, если зафиксирована попытка обратиться к несуществующей переменной, оборвалось интернет-соединение или не найден файл. Механизм называется «обработка исключений» (exception handling).

Исключение — это объект любого типа: строка, число, число с плавающей точкой и т. д. Он необходим для уведомления об ошибке. Задача исключения — передать сведения из точки ошибки тому, кто должен ее обработать. На примере ниже разберем, как это работает.

Синтаксис достаточно простой:

try
{
здесь код, который может выбросить исключение Throw
}
catch
{
обработка исключения
}
c++

В некоторых случаях используют finally. Тогда блок кода будет выполнен в любом случае, вне зависимости от того, выпала ли ошибка. Finally обеспечивает выполнение критически важного кода: например, освобождение ресурсов, закрытие файлов, соединение с базой данных, даже если произошел сбой в коде.

Для примера возьмем ситуацию с работой в офисе.

int main() {

    try {

идти на работу;

получать задачи;

писать код;

throw "Заболел!";

тестировать приложение;

созваниваться с менеджером

            } 

catch {

искать замену сотруднику

          }

}
c++
Как работает try, throw и catch на примере заболевшего сотрудника
Как работает 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
 }
c++

Писать, тестировать и документировать код можно в отечественной среде разработки GigaIDE Desktop. В ней доступен умный ассистент GigaCode. В режиме code chat он объясняет код, делает ревью, предлагает улучшения и делает многое другое.

Деление на ноль на C++ в среде разработки GigaIDE Desktop
Деление на ноль на C++ в среде разработки GigaIDE Desktop

В данном случае закономерно появляется ошибка, о которой выше упомянул GigaCode.

Ошибка деления на ноль на C++
Ошибка деления на ноль на C++

Ниже попробуем устранить проблему и добавить хендлинг.

Примеры использования конструкции 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;
}
c++
Обработка ошибок на C++ с пояснением от GigaCODE в режиме code chat
Обработка ошибок на C++ с пояснением от GigaCODE в режиме code chat

Ситуации, когда используются обработчики, могут быть разными. Известны error при работе с:

  • динамической памятью;
  • базами данных;
  • окнами, а также многие другие.

Ниже расскажем, какие инструменты предоставляет язык программирования C++.

Ловить исключения нескольких типов

Разработчик может предусмотреть возможность отловить исключения разных типов. Синтаксис:

catch(ExceptionType1 e) {
        }
catch(ExceptionType2 e) {
}
c++

Отловить все исключения

Подходит для ситуации, когда разработчик указал несколько блоков обработчиков в коде выше. Синтаксис catch(...) {} означает «поймать все остальные исключения».

Перебросить исключение 

Если текущий обработки не может справиться с ошибкой, можно перебросить его другому обработчику:

catch(...) {throw}
c++

Альтернативный вариант:

std::rethrow_exception(std::current_exception())
c++

Но стоит учитывать, что 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;
}
c++

Собственные пользовательские классы

На основе базового 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(); }
}.
c++

Частые ошибки при обработке исключений

Среди основных ошибок, с которыми могут столкнуться разработчики:

  • не добавили блок catch для хедлинга ошибок;
  • добавили блок, но оставили часть исключений необработанными;
  • использовали catch для проверки валидности или других задач в нормальном потоке программы (не для работы с критическими ситуациями).

Возможны и более сложные ошибки: 

  • использование set_terminate и set_unexpected (считается legacy-кодом);
  • noexcept с указанием на то, что функция не выбрасывает исключения (а очень часто как раз она и становится причиной проблем в коде);
  • передача исключений между потоками через аргументы функций или глобальные переменные.

Exception Handling — инструмент, который позволяет писать работающий, поддерживаемый и предсказуемый код. Важно правильно использовать его, а помочь в этом могут не только старшие коллеги, но и AI-ассистенты. Например, GigаCODE в режиме code chat не только объясняет код, но и делает ревью или предлагает улучшения. Чтобы запросить информацию, можно использовать хинты /review или /improve. Также доступны:

  • /doc (сгенерировать документацию); 
  • /explain (объяснить код);
  • /test (написать код);
  • /explain_step_by_step (объяснить пошагово).
GigaCode делает ревью кода на C++
GigaCode делает ревью кода на C++