ПрограммированиеC++ Backend разработчик

Как работает система исключений (exceptions) в C++, и чем она отличается от других языков? Как правильно проектировать обработку исключений в промышленных приложениях?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Система исключений в C++ основана на механизме try-catch и ключевом слове throw. При возникновении исключительной ситуации (например, ошибка ресурса, нарушения инварианта), внутри блока try инициируется (бросается) исключение через оператор throw. Операции по поиску подходящего обработчика (catch) осуществляются путём показа стека до первого совпадающего типа.

Важные детали:

  • В отличие от Java или C#, исключения в C++ могут быть любого типа (обычно класс, производный от std::exception).
  • В C++ нет обязательной спецификации исключений (как checked exceptions в Java), вместо этого в C++11 и далее введён спецификатор noexcept.
  • При броске исключения происходит вызов деструкторов для всех объектов на стеке между точкой выброса и точкой обработки — важно поддерживать RAII.

Проектирование:

  • Кидайте только исключения "неожидаемых" ошибок, для прочего используйте return код.
  • Захватывайте по константной ссылке — catch(const std::exception& e).
  • GNU рекомендует бросать объекты, копируемые, а не указатели (избегайте памяти на куче).

Пример кода:

#include <iostream> #include <stdexcept> void mayFail(bool fail) { if (fail) throw std::runtime_error("Ошибка процесса"); } int main() { try { mayFail(true); } catch (const std::exception& ex) { std::cout << "Поймано исключение: " << ex.what() << std::endl; } }

Вопрос с подвохом

Что произойдёт, если исключение будет выброшено из деструктора во время передачи другого исключения вверх по стеку?

Ответ: Произойдёт аварийное завершение программы через вызов std::terminate(), потому что во время обработки исключения не допускается генерация нового исключения (double exception), иначе нарушается stack unwinding.

Пример:

struct CrashOnDestruct { ~CrashOnDestruct() noexcept(false) { throw std::runtime_error("Ошибка в деструкторе!"); } }; void func() { CrashOnDestruct obj; throw std::logic_error("Обработка ошибки"); } int main() { try { func(); } catch (...) { } } // Завершится через std::terminate()

Примеры реальных ошибок из-за незнания тонкостей темы


История

Внутри крупного финансового приложения исключения бросались из деструктора класса-обёртки над базой данных. Одна из опорных транзакций при возникновении ошибки инициировала исключение, при unwind-выходе вызывался деструктор, который также кидал ошибку. Всё приложение аварийно завершалось, теряя работу пользователей. Разработчикам пришлось срочно заменить throw в деструкторах на запись логов и корректную обработку.


История

Микросервис обрабатывал исключения по типу: catch(std::exception). Один из потоков бросал исключения пользовательских типов (не наследовались от std::exception). Такие ошибки не отлавливались, происходил неожиданный обрыв соединения и утечка памяти.


История

Отсутствие спецификатора noexcept в методах перемещения контейнеров привело к тому, что стандартная библиотека использовала медленную копию вместо быстрой move-операции; существенный провал в производительности был выявлен только на нагрузочных тестах.