Обработка исключений в C++
Язык С представляет программисту очень ограниченные возможности обработки исключений, возникших при работе программы. В этом отношении С++ намного развитее С. Здесь у программиста существенно большие возможности по непосредственной обработке исключений. Комитет по разработке стандартов С++ предоставил очень простую, но мощную форму обработки исключений.
Темные дни С
Типичная функция, написанная на С, выглядит примерно так:
long DoSomething() < long *a, c; FILE *b; a = malloc(sizeof(long) * 10); if (a == NULL) return 1; b = fopen("something.bah", "rb"); if (b == NULL) < free(a); return 2; >fread(a, sizeof(long), 10, b); if (a[0] != 0x10) < free(a); fclose(b); return 3; >fclose(b); c = a[1]; free(a); return c; >
Выглядит не очень, не так ли? Вы целиком и полностью зависите от значений, которые возвращают вам функции и для каждой ошибки вам постоянно нужен код, который ее обрабатывает. Если вы, скажем, в функции работаете хотя бы с 10 указателями (рапределяете память, освобождаете ее и т.д.), то наверняка половину кода функции будет занимать код обработки ошибок. Такая же ситуация будет в коде, вызывающем эту функцию, так как здесь также нужно обработать все возвращаемые коды ошибок.
Try-catch-throw
Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:
- try (пытаться) — начало блока исключений;
- catch (поймать) — начало блока, «ловящего» исключение;
- throw (бросить) — ключевое слово, «создающее» («возбуждающее») исключение.
А теперь пример, демонстрирующий, как применить то, что вы узнали:
void func() < try < throw 1; >catch(int a) < cout cout
Если выполнить этот фрагмент кода, то мы получим следующий результат:
Caught exception number: 1
Теперь закоментируйте строку throw 1; и функция выдаст такой результат:
No exception detected!
Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может «ловить» любой тип данных, так же как и throw может «кинуть» данные любого типа. Т.е. throw AnyClass(); будет правильно работать, так же как и catch (AnyClass &d) <>;.
Как уже было сказано, catch может «ловить» данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого:
catch(dumbclass)
catch(dumbclass&)
Так же можно «поймать» и все исключения:
catch(. )
Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если «кидаются» данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше «ловить» их по ссылке, иначе вся «кидаемая» переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков catch, ловящих «свой» тип данных:
try < throw 1; // throw 'a'; >catch (long b) < cout catch (char b) < cout "
Создание" исключений
Когда возбуждается исключительная ситуация, программа просматривает стек функций до тех пор, пока не находит соответствующий catch. Если оператор catch не найден, STL будет обрабатывать исключение в стандартном обработчике, который делает все менее изящно, чем могли бы сделать вы, показывая какие-то непонятные (для конечного пользователя) сообщения и обычно аварийно завершая программу.
Однако более важным моментом является то, что пока просматривается стек функций, вызываются деструкторы всех локальных классов, так что вам не нужно забодиться об освобождении памяти и т.п.
Перегрузка глобальных операторов new/delete
А сейчас хотелось бы отправить вас к статье "Как обнаружить утечку памяти". В ней рассказывается, как обнаружить неправильное управление распределением памяти в вашей программе. Вы можете спросить, при чем тут перегрузка операторов? Если перегрузить стандартные new и delete, то открываются широкие возможности по отслеживанию ошибок (причем ошибок часто критических) с помощью исключений. Например:
char *a; try < a = new char[10]; >catch (. ) < // a не создан - обработать ошибку распределения памяти, // выйти из программы и т.п. >// a успешно создан, продолжаем выполнение
Это, на первый взгляд, кажется длиннее, чем стандартная проверка в С "а равен NULL?", однако если в программе выделяется десяток динамических переменных, то такой метод оправдывает себя.
Операторы throw без параметров
Итак, мы увидели, как новый метод обработки ошибок удобен и прост. Блок try-catch может содержать вложенные блоки try-catch и если не будет определено соответствующего оператора catch на текущем уровен вложения, исключение будет поймано на более высоком уровне. Единственная вещь, о которой вы должны помнить, - это то, что операторы, следующие за throw, никогда не выполнятся.
try < throw; // ни один оператор, следующий далее (до закрывающей скобки) // выполнен не будет >catch(. )
Такой метод может применяться в случаях, когда не нужно передавать никаких данных в блок catch.
Приложение
Приведем пример, как все вышеизложенное может быть использовано в конкретном приложении. Преположим, у вас в программе есть класс cMain и экземпляр этого класса Main: class cMain < public: bool Setup(); bool Loop(); // Основной цикл программы void Close(); >; cMain Main;
А в функции main() или WinMain() вы можете использовать этот класс как-нибудь так:
try < Main.Setup(); Main.Loop(); Main.Close(); >catch (Exception &e) < // использование класса, ведущего лог. log("Exception thrown: %s", e.String()); // Показываем сообщение об ошибке и закрываем приложение. >
Основной цикл программы может выглядеть примерно так:
while (AppActive) < try < // какие-то действия >catch (Exception &e) < /* Если исключение критическое, типа ошибки памяти, посылаем исключение дальше, в main(), оператором throw e; или просто throw. Если исключение некритично, обрабатываем его и возвращаемся в основной цикл. */ >>
Заключение
Метод обработки исключений, приведенный в статье, является удобным и мощным средством, однако только вам решать, использовать его или нет. Одно можно скачать точно - приведенный метод облегчит вам жизнь. Если хотите узнать об исключениях чуть больше, посмотрите публикацию Deep C++ на сервере MSDN.
Операторы try, throw и catch (C++)
Для реализации обработки исключений в C++используется try throw и catch выражения.
Во-первых, используйте try блок для заключения одного или нескольких операторов, которые могут вызвать исключение.
throw Выражение сигнализирует о том, что исключительное условие ( часто ошибка) произошла в блоке try . Объект любого типа можно использовать в качестве операнда throw выражения. Обычно этот объект используется для передачи информации об ошибке. В большинстве случаев рекомендуется использовать std::exception класс или один из производных классов, определенных в стандартной библиотеке. Если один из них не подходит, рекомендуется наследовать собственный класс исключений. std::exception
Для обработки исключений, которые могут быть возникают, реализуйте один или несколько catch блоков сразу после try блока. Каждый catch блок указывает тип исключения, который он может обрабатывать.
В этом примере показан try блок и его обработчики. Предположим, GetNetworkResource() получает данные через сетевое подключение, а 2 типа исключений являются определенными пользователем классами, производными от std::exception . Обратите внимание, что исключения перехватываются по const ссылке в инструкции catch . Рекомендуется создавать исключения по значению и захватывать их ссылкой константы.
Пример
MyData md; try < // Code that could throw an exception md = GetNetworkResource(); >catch (const networkIOException& e) < // Code that executes when an exception of type // networkIOException is thrown in the try block // . // Log error message in the exception object cerr catch (const myDataFormatException& e) < // Code that handles another exception type // . cerr // The following syntax shows a throw expression MyData GetNetworkResource() < // . if (IOSuccess == false) throw networkIOException("Unable to connect"); // . if (readError) throw myDataFormatException("Format error"); // . >
Замечания
Код после try предложения является защищенным разделом кода. Выражение throw вызывает исключение. Блок кода после catch предложения является обработчиком исключений. Это обработчик, который перехватывает исключение, которое создается, если типы в throw выражениях catch совместимы. Список правил, которые управляют сопоставлением типов в catch блоках, см. в разделе "Оценка блоков catch". catch Если инструкция задает многоточие (. ) вместо типа, catch блок обрабатывает каждый тип исключения. При компиляции с /EHa параметром они могут включать структурированные исключения C и системные или асинхронные исключения, созданные приложением, такие как защита памяти, разделение на ноль и нарушения с плавающей запятой. Так как catch блоки обрабатываются в программе, чтобы найти соответствующий тип, обработчик многоточия должен быть последним обработчиком для связанного try блока. Используйте с осторожностью; не позволяйте catch(. ) программе продолжать работу, если блок catch не знает, как обрабатывать определенное исключение, которое поймано. Как правило, блок catch(. ) используется для ведения журнала ошибок и выполнения специальной очистки перед остановкой выполнения программы.
Выражение throw , которое не имеет операнда повторно выполняет обработку исключения. Мы рекомендуем эту форму при повторном создании исключения, так как это сохраняет сведения о полиморфном типе исходного исключения. Такое выражение должно использоваться только в catch обработчике или в функции, которая вызывается из обработчика catch . Объект исключения rethrown является исходным объектом исключения, а не копией.
try < throw CSomeOtherException(); >catch(. ) < // Catch all exceptions - dangerous. // Respond (perhaps only partially) to the exception, then // re-throw to pass the exception to some other handler // . throw; >
Что такое throw в C++?
Ключевое слово throw используется для генерации исключений в программе. Исключения позволяют программистам обрабатывать ошибки и непредвиденные ситуации более эффективно. Рассмотрим основные аспекты и пример использования throw в C++.
#include #include int divide(int numerator, int denominator) < if (denominator == 0) < // Генерация исключения в случае деления на ноль throw std::runtime_error("Попытка деления на ноль!"); >return numerator / denominator; > int main() < try < int result = divide(10, 0); std::cout catch (const std::exception& e) < // Обработка исключения std::cerr return 0; >
В этом примере функция divide генерирует исключение типа std::runtime_error , если второй аргумент (знаменатель) равен нулю. В блоке try в функции main вызывается divide с делением на ноль, и исключение перехватывается в блоке catch , где выводится сообщение об ошибке.
Обработка исключений C++

Исключение – это проблема, возникающая во время выполнения программы. Исключение C++ – это реакция на особые обстоятельства, возникающие во время выполнения программы, такие как попытка деления на ноль.
Исключения позволяют передавать управление из одной части программы в другую. Обработка исключений C++ построена на трех ключевых словах: try, catch и throw.
- throw-программа выдает исключение, когда появляется проблема. Это делается с помощью ключевого слова throw.
- catch-программа ловит исключение с помощью обработчика исключений в том месте программы, где вы хотите обработать проблему. Ключевое слово catch указывает на перехват исключения.
- try-блок try определяет блок кода, для которого будут активированы определенные исключения. За ним следует один или несколько блоков catch.
Предполагая, что блок вызовет исключение, метод ловит исключение, используя комбинацию ключевых слов try и catch. Блок try/catch помещается вокруг кода, который может генерировать исключение. Код внутри блока try/catch называется защищенным кодом, а синтаксис использования try/catch выглядит следующим образом:
> catch( ExceptionName e1 )
> catch( ExceptionName e2 )
> catch( ExceptionName eN )
Вы можете указать несколько операторов catch, чтобы перехватить различные типы исключений в случае, если ваш блок try вызывает более одного исключения в разных ситуациях.
Почему Обработка Исключений?
Ниже приведены основные преимущества обработки исключений по сравнению с традиционной обработкой ошибок.
- Отделение кода обработки ошибок от обычного кода: в традиционных кодах обработки ошибок всегда существуют условия if else для обработки ошибок. Эти условия и код для обработки ошибок смешиваются с нормальным потоком. Это делает код менее читаемым и поддерживаемым. С блоками try catch код для обработки ошибок становится отдельным от обычного потока.
- Функции / Методы могут обрабатывать любые исключения, которые они выбирают: функция может выдавать много исключений, но может выбрать обработку некоторых из них. Другие исключения, которые выбрасываются, но не перехвачены, могут быть обработаны вызывающим. Если вызывающий абонент решает не перехватывать их, то исключения обрабатываются вызывающим абонентом вызывающего абонента. В C++ функция может указать исключения, которые она создает, используя ключевое слово throw. Вызывающий эту функцию должен каким-то образом обработать исключение (либо указав его снова, либо поймав)
- Группировка типов ошибок: в C++ как базовые типы, так и объекты могут быть выброшены как исключение. Мы можем создавать иерархию объектов исключений, группировать исключения в пространствах имен или классах, классифицировать их по типам.
Генерирование исключений
Исключения могут быть созданы в любом месте блока кода с помощью оператора throw. Операнд оператора throw определяет тип исключения и может быть любым выражением, а тип результата выражения определяет тип создаваемого исключения.
Ниже приведен пример создания исключения при выполнении условия деления на ноль:
double division(int a, int b)
throw “Division by zero condition!”;
Перехват Исключений
Блок catch, следующий за блоком try, ловит любое исключение. Вы можете указать, какой тип исключения вы хотите перехватить, и это определяется объявлением исключения, которое появляется в круглых скобках после ключевого слова catch.
// protected code> catch( ExceptionName e )
// code to handle ExceptionName exception
Приведенный выше код перехватит исключение типа ExceptionName. Если вы хотите указать, что блок catch должен обрабатывать любой тип исключения, вызванного в блоке try, вы должны поставить многоточие,…, между скобками, заключающими объявление исключения следующим образом:
// code to handle any exception
Стандартные Исключения C++
C++ предоставляет список стандартных исключений, определенных в , которые мы можем использовать в наших программах. Они расположены в иерархии родительско-дочерних классов, показанной ниже –

Иерархия классов исключений
Понравилось то, что вы прочитали?
Подписывайтесь на нашу рассылку и получайте ежедневные обновления о новых учебниках, статьях, курсах и о многом другом!
Просто введите ваш адрес электронной почты, чтобы подписаться.
(Без спамов; ежемесячно два письма; отписаться от рассылки можно в любое время)
Спасибо!
Подписка успешно оформлена.