Что такое обработка исключений c
Перейти к содержимому

Что такое обработка исключений c

  • автор:

Обработка исключений

Обработка исключений (exception handling) позволяет упорядочить обработку ошибок времени исполнения. Используя обработку исключений С++, программа может автоматически вызвать функцию-обработчик ошибок тогда, когда такая ошибка возникает. Принципиальным достоин­ством обработки исключений служит то, что она позволяет автоматизировать большую часть кода для обработки ошибок, для чего раньше требовалось ручное кодирование.

Основы обработки исключений

Обработка исключений в С++ использует три ключевых слова: try, catch и throw. Те инструкции программы, где ожидается возможность появления исключительных ситуаций, содержатся в бло­ке try. Если в блоке try возникает исключение, т. е. ошибка, то генерируется исключение. Исклю­чение перехватывается, используя catch, и обрабатывается. Ниже это общее описание будет рас­смотрено более подробно.

Инструкция, генерирующая исключение, должна исполняться внутри блока try. Вызванные из блока try функции также могут генерировать исключения. Всякое исключение должно быть пере­хвачено инструкцией catch, которая непосредственно следует за инструкцией try, сгенерировав­шей исключение. Общая форма блоков try и catch показана ниже:

try // блок try
catch (тип1 аргумент) // блок catch
catch (тип2 аргумент) // блок catch
catch (типЗ аргумент) // блок catch
>
.
catch (типN аргумент) // блок catch
>

Размеры блока try могут изменяться в больших пределах. Например, блок try может содержать несколько инструкций какой-либо функции, либо же, напротив, включать в себя весь код функ­ции main(), так что вся программа будет охвачена обработкой исключений.

Когда исключение сгенерировано, оно перехватывается соответствующей инструкцией catch, обрабатывающей это исключение. Одному блоку try может отвечать несколько инструкций catch, Какая именно инструкция catch исполняется, зависит от типа исключения. Это означает, что если тип данных, указанных в инструкции catch, соответствует типу данных исключения, то только эта инструкция catch и будет исполнена. Когда исключение перехвачено, arg получает ее значение. Перехваченным может быть любой тип данных, включая созданные программистом классы. Если никакого исключения не сгенерировано, то есть никакой ошибки не возникло в блоке try, то инструкции catch выполняться не будут.

Общая форма записи инструкции throw имеет вид:

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

Если генерируется исключение, для которого отсутствует подходящая инструкция catch, может произойти аварийное завершение программы. При генерации необработанного исключения
вызывается функция terminate(). По умолчанию terminate() вызывает функцию abort(), завершаю­щую выполнение программы. Однако можно задать свою собственную обработку, используя фун­кцию set_terminate(). Подробности можно найти в документации к компилятору.

Ниже представлен пример, иллюстрирующий способ обработки исключений в С++:

Программа выведет на экран следующий текст:

Start
Inside try block
Caught an exception — value is: 100
End

Рассмотрим внимательнее эту программу. Как можно видеть, блок try содержит три инструкции. За ним следует инструкция catch(int i), обрабатывающая исключения целого типа. В блоке try будут выполняться только две инструкции: первая и вторая — throw. Как только исключение было сгенерировано, управление передается инструкции catch, а блок try прекращает свое исполнение. Таким образом, catch не вызывается. Скорее можно сказать, что к ней переходит исполнение программы. Для этого автоматически осуществляется переустановка стека. Таким образом, инст­рукция после инструкции throw никогда не выполняется.

Обычно код в инструкции catch пытается исправить ошибку путем выполнения подходящих действий. Если ошибку удалось исправить, то выполнение продолжается с инструкции, непосред­ственно следующей за catch. Однако иногда не удается справиться с ошибкой, и блок catch завер­шает программу путем вызова функции exit() или функции abort().

Как отмечалось, тип исключения должен соответствовать указанному в инструкции типу. Напри­мер, в предыдущем примере если изменить тип инструкции catch на double, то исключение не будет перехвачено и произойдет аварийное завершение программы. Такое изменение показано ниже:

Эта программа выдаст следующий результат, поскольку исключение целого типа не будет пере­хвачено инструкцией catch (double i):

Start
Inside try block
Abnormal program termination

Исключение может также быть сгенерировано из функции, вызванной изнутри блока try. В качестве примера рассмотрим следующую программу:

Эта программа выдаст следующий результат:

Start
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught an exception — value is: 1
End

Блок try может быть локализован в какой-то функции. В таком случае всякий раз при входе в функцию начинается обработка исключений. В качестве примера рассмотрим следующую про­грамму:

#include
// try/catch могут находиться в функции вне main()
void Xhandler(int test)
try if (test) throw test;
>
catch(int i) cout >
>
int main()
cout Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout return 0;
>

Эта программа выдаст на экран следующий текст:

Start
Caught Exception #: 1
Caught Exception #: 2
Caught Exception #: 3
End

Как видно, сгенерировано три исключения. После каждого исключения функция возвращает управ­ление в функцию main. При каждом новом вызове функции возвращается обработка исключений.

Важно понимать, что код, ассоциированный с инструкцией catch, будет исполняться только тогда, когда перехвачено исключение. В противном случае выполнение программы просто обой­дет инструкцию catch.

Исключения

В процессе работы программы могут возникать различные ошибки. Например, при передаче файла по сети оборвется сетевое подключение или будут введены некорректные и недопустимые данные, которые вызовут падение программы. Такие ошибки еще называются исключениями. Исключение представлякт временный объект любого типа, который используется для сигнализации об ошибке. Цель объекта-исключения состоит в том, чтобы передать информацию из точки, в которой произошла ошибка, в код, который должен ее обработать. Если исключение не обработано, то при его возникновении программа прекращает свою работу.

Например, в следующей программе происходит деление чисел:

#include double divide(int a, int b) < return a / b; >int main() < int x; int y<>; double z ; std::cout

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

С одной стороны, мы можем в функции divide определить проверку и выполнять деление, если параметр b не равен 0. Однако нам в любом случае надо возвращать из функции divide некоторый результат — некоторое число. То есть мы не можем просто написать:

double divide(int a, int b) < if (b) return a / b; else std::cout

И в этом случае нам надо известить систему о возникшей ошибке. Для этого используется оператор throw .

Оператор throw генерирует исключение. Через оператор throw можно передать информацию об ошибке. Например, функция divide могла бы выглядеть следующим образом:

double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >

То есть если параметр b равен 0, то генерируем исключение.

Но это исключение еще надо обработать в коде, где будет вызываться функция divide. Для обработки исключений применяется конструкция try. catch . Она имеет следующую форму:

try < инструкции, которые могут вызвать исключение >catch(объявление_исключения)

В блок кода после ключевого слова try помещается код, который потенциально может сгенерировать исключение.

После ключевого слова catch в скобках идет параметр, который передает информацию об исключении. Затем в блоке производится собственно обработка исключения.

Так изменим весь код следующим образом:

#include double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >int main() < int x; int y<>; try < double z ; std::cout catch (. ) < std::cout std::cout

Код, который потенциально может сгенерировать исключение — вызов функции divide помещается в блок try.

В блоке catch идет обработка исключения. Причем многоточие в скобках после оператора catch ( catch(. ) ) позволяет обработать любое исключение.

В итоге когда выполнение программы дойдет до строки

double z ;

При выполнении этой строки будет сгенерировано исключение, поэтому последующие инструкции из блока try выполняться не будут, а управление перейдет в блок catch, в котором на консоль просто выводится сообщение об ошибке. После выполнения блока catch программа аварийно не завершится, а продолжит свою работу, выполняя операторы после блока catch:

Error! The End.

Однако в данном случае мы только знаем, что произошла какая-то ошибка, а какая именно, неизвестно. Поэтому через параметр в блоке catch мы можем получить то сообщение, которое передается оператору throw :

#include >iostream < double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >int main() < int x; int y<>; try < double z ; std::cout catch (const char* error_message) < std::cout std::cout

С помощью параметра const char* error_message получаем сообщение, которое предано оператору throw, и выводим это сообщение на консоль. Почему здесь мы получаем сообщение об ошибке в виде типа const char* ? Потому что после оператора throw идет строковый литерал, который представляет как раз тип const char* . И в этом случае консольный вывод будет выглядеть следующим образом:

Division by zero! The End.

Таким образом, мы можем узнать суть возникшего исключения. Подобным образом мы можем передавать информацию об исключении через любые типы, например, std::string:

throw std::string;

Тогда в блоке catch мы можем получить эту информацию в виде объекта std::string :

catch (std::string error_message)

Если же исключение не обработано, то вызывается функция std::terminate() (из модуля стандартной библиотеки C++), которая, в свою очередь, по умолчанию вызывает другую функцию — std::abort() (из ), которая собственно и завершает программу.

Существует очень много функций и в стандартной библиотеке С++, и в каких-то сторонних библиотеках. И может возникнуть вопрос, какие из них вызывать в конструкции try-catch, чтобы не столкнуться с необработанным исключением и аварийным завершением программы. В этом случае может помочь прежде всего документация по функции (при ее наличии). Другой сигнал — ключевое слово noexcept , которое при использовании в заголовке функции указывает, что эта функция никогда не будет генерировать исключения. Например:

void print(int argument) noexcept;

Здесь указываем, что функция print() никогда не вызовет исключение. Таким образом, встретив функцию с подобным ключевым словом, можно ожидать, что она не вызовет исключения. И соответственно нет необходимости помещать ее вызов в конструкцию try-catch.

Создание объекта исключения

При обработке исключения стоит помнить, что при передаче объекта оператору throw блок catch получает копию этого объекта. И эта копия существует только в пределах блока catch.

Для значений примитивных типов, например, int , копирование значения может не влиять на производительность программы. Однако при передаче объектов классов издержки могут выше. Поэтому в этом случае объекты обычно передаются по ссылке, например:

#include double divide(int a, int b) < if (b) return a / b; throw std::string; > int main() < int x; int y<>; try < double z ; std::cout catch (const std::string& error_message) // строка передается по ссылке < std::cout std::cout

Обработка и генерация разных типов исключений

Мы можем генерировать и обрабатывать несколько разных исключительных ситуаций. Допустим, нам надо, чтобы при делении делитель был меньше делимого:

#include double divide(int a, int b) < if(!b) // если b == 0 < throw 0; >if(b > a) < throw "The first number is greater than the second one"; >return a / b; > void test(int a, int b) < try < double result ; std::cout catch (int code) < std::cout catch (const char* error_message) < std::cout > int main() < test(100, 20); // 5 test(100, 0); // Error code: 0 test(100, 1000); // The first number is greater than the second one >

В функции divide в зависимости от значения числа b оператору throw передаем либо число:

throw 0;

либо строковый литерал:

throw "The first number is greater than the second one";

Для тестирования функции divide определена другая функция — test, где вызов функции divide() помещен в конструкцию try..catch . Поскольку при генерации исключения мы можем получать ошибку в виде двух типов — int (если b равно 0) и const char* (если b больше a), то для обработки каждого типа исключений определены два разных блока catch:

catch (int code) < std::cout catch (const char* error_message)

В функции main вызываем функцию test, передавая в нее различные числа. При вызове:

test(100, 20); // 5

число b не равно 0 и меньше a, поэтому никаких исключений не возникает, блок try срабатывает до конца, и функция завершает свое выполнение.

При втором вызове

test(100, 0); // Error code: 0

число b равно 0, поэтому генерируется исключение, а в качестве объекта исключения передается число 0. Поэтому при возникновении исключения программа выберет тот блок catch, где обрабатывается исключения типа int:

catch (int code)

При третьем вызове

test(100, 1000); // The first number is greater than the second one

число b больше a, поэтому объект исключения будет представлять строковый литерал или const char* . Поэтому при возникновении исключения программа выберет блок catch, где обрабатывается исключения типа const char*:

catch (const char* error_message)

Таким образом, в данном случае мы получим следующий консольный вывод:

5 Error code: 0 The first number is greater than the second one

Может быть ситуация, когда генерируется исключение внутри конструкции try-catch , и даже есть блок catch для обработки исключений, однако он обрабатывает другие типы исключений:

void test(int a, int b) < try < double result ; std::cout catch (const char* error_message) < std::cout >

Здесь нет блока catch для обработки исключения типа int . Поэтому при генерации исключения:

throw 0;

Программа не найдет нужный блок catch для обработки исключения, и программа аварийно завершит свое выполнение.

try-catch и деструкторы

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

#include class Person < public: Person(std::string name) :name< name > < std::cout ~Person() < std::cout void print() < throw "Print Error"; >private: std::string name; >; int main() < try < Person tom< "Tom" >; tom.print(); // Здесь генерируется ошибка > catch (const char* error) < std::cerr >

В классе Person определяет деструктор, который выводит сообщение на консоль. В функции print просто генерируем исключение.

В функции main в блоке try создаем один объект Person и вызываем у него функцию print, что естественно приведет к генерарции исключения и переходу управления программы в блок catch . И если мы посмотрим на консольный вывод

Person Tom created Person Tom deleted Print Error

то мы увидим, что прежде чем начнется обработка исключения в блоке catch, будет вызван деструктор объекта Person.

C # — Обработка исключений

Исключением является проблема, возникающая во время выполнения программы. Исключение AC # — это ответ на исключительное обстоятельство, которое возникает во время работы программы, например попытка деления на ноль.

Исключения обеспечивают способ передачи контроля из одной части программы в другую. Обработка исключений C # построена на четырех ключевых словах: try , catch , finally и throw .

  • try — блок try идентифицирует блок кода, для которого активируются определенные исключения. За ним следует один или несколько блоков catch .
  • catch — программа выхватывает исключение с обработчиком исключений в месте в программе, где вы хотите справиться с этой проблемой. Ключевое слово catch указывает на улавливание исключения.
  • finally — блок finally используется для выполнения заданного набора операторов, независимо от того, выбрано или не выбрано исключение. Например, если вы открываете файл, он должен быть закрыт независимо от того, создано ли исключение или нет.
  • throw — программа выдает исключение, когда возникает проблема. Это делается с использованием ключевого слова throw .

Синтаксис

Предполагая, что блок вызывает исключение, метод выбирает исключение, используя комбинацию ключевых слов try и catch . Блок try / catch помещается вокруг кода, который может генерировать исключение. Код внутри блока try / catch называется защищенным кодом, а синтаксис использования try / catch выглядит следующим образом:

try < // statements causing exception >catch( ExceptionName e1 ) < // error handling code >catch( ExceptionName e2 ) < // error handling code >catch( ExceptionName eN ) < // error handling code >finally < // statements to be executed >

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

Классы исключений в C #

Исключения C # представлены классами. Исключительные классы в C # в основном прямо или косвенно получены из класса System.Exception . Некоторые классы исключений, полученные из класса System.Exception, представляют собой классы System.ApplicationException и System.SystemException .

Класс System.ApplicationException поддерживает исключения, создаваемые прикладными программами. Следовательно, исключения, определенные программистами, должны вытекать из этого класса.

Класс System.SystemException является базовым классом для всех предопределенных системных исключений.

В следующей таблице приведены некоторые предопределенные классы исключений, полученные из класса Sytem.SystemException :

Обрабатывает ошибки ввода-вывода.

Обрабатывает ошибки, возникающие, когда метод ссылается на индекс массива вне диапазона.

Обрабатывает ошибки, возникающие, когда тип несовместим с типом массива.

Обрабатывает ошибки, возникающие при ссылке на нулевой объект.

Обрабатывает ошибки, возникающие при делении дивиденда на ноль.

Обрабатывает ошибки, возникающие при типизации.

System.OutOfMemoryException

Обрабатывает ошибки, возникающие из-за недостаточной свободной памяти.

System.StackOverflowException

Обрабатывает ошибки, возникающие при переполнении стека.

Обработка исключений

C # обеспечивает структурированное решение обработки исключений в виде блоков try и catch . Используя эти блоки, операторы основной программы отделены от операторов обработки ошибок.

Эти блоки обработки ошибок реализованы с использованием ключевых слов try , catch и finally . Ниже приведен пример исключения исключения при делении на нулевое условие:

using System; namespace ErrorHandlingApplication < class DivNumbers < int result; DivNumbers() < result = 0; >public void division(int num1, int num2) < try < result = num1 / num2; >catch (DivideByZeroException e) < Console.WriteLine("Exception caught: ", e); > finally < Console.WriteLine("Result: ", result); > > static void Main(string[] args) < DivNumbers d = new DivNumbers(); d.division(25, 0); Console.ReadKey(); >> >

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Exception caught: System.DivideByZeroException: Attempted to divide by zero. at . Result: 0

Создание пользовательских исключений

Вы также можете определить свое собственное исключение. Пользовательские классы исключений выводятся из класса Exception . Следующий пример демонстрирует это:

using System; namespace UserDefinedException < class TestTemperature < static void Main(string[] args) < Temperature temp = new Temperature(); try < temp.showTemp(); >catch(TempIsZeroException e) < Console.WriteLine("TempIsZeroException: ", e.Message); > Console.ReadKey(); > > > public class TempIsZeroException: Exception < public TempIsZeroException(string message): base(message) < >> public class Temperature < int temperature = 0; public void showTemp() < if(temperature == 0) < throw (new TempIsZeroException("Zero Temperature found")); >else < Console.WriteLine("Temperature: ", temperature); > > >

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

TempIsZeroException: Zero Temperature found

Метание объектов

Вы можете бросить объект, если он прямо или косвенно получен из класса System.Exception . Вы можете использовать оператор throw в блоке catch, чтобы передать данный объект как:

Основы обработки исключений

Далеко не всегда ошибки случаются по вине того, кто кодирует приложение. Иногда приложение генерирует ошибку из-за действий конечного пользователя, или же ошибка вызвана контекстом среды, в которой выполняется код. В любом случае вы всегда должны ожидать возникновения ошибок в своих приложениях и проводить кодирование в соответствии с этими ожиданиями.

В .NET Framework предусмотрена развитая система обработки ошибок. Механизм обработки ошибок C# позволяет закодировать пользовательскую обработку для каждого типа ошибочных условий, а также отделить код, потенциально порождающий ошибки, от кода, обрабатывающего их.

Что бы ни служило причиной проблем, в конечном итоге приложение начинает работать не так, как ожидается. Прежде чем переходить к рассмотрению структурированной обработки исключений, давайте сначала ознакомимся с тремя наиболее часто применяемыми для описания аномалий терминами:

Программные ошибки (bugs)

Так обычно называются ошибки, которые допускает программист. Например, предположим, что приложение создается с помощью неуправляемого языка С++. Если динамически выделяемая память не освобождается, что чревато утечкой памяти, появляется программная ошибка.

Пользовательские ошибки (user errors)

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

Исключения (exceptions)

Исключениями, или исключительными ситуациями, обычно называются аномалии, которые могут возникать во время выполнения и которые трудно, а порой и вообще невозможно, предусмотреть во время программирования приложения. К числу таких возможных исключений относятся попытки подключения к базе данных, которой больше не существует, попытки открытия поврежденного файла или попытки установки связи с машиной, которая в текущий момент находится в автономном режиме. В каждом из этих случаев программист (и конечный пользователь) мало что может сделать с подобными «исключительными» обстоятельствами.

По приведенным выше описаниям должно стать понятно, что структурированная обработка исключений в .NET представляет собой методику, предназначенную для работы с исключениями, которые могут возникать на этапе выполнения. Даже в случае программных и пользовательских ошибок, которые ускользнули от глаз программиста, однако, CLR будет часто автоматически генерировать соответствующее исключение с описанием текущей проблемы. В библиотеках базовых классов .NET определено множество различных исключений, таких как FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeException и т.д.

В терминологии .NET под «исключением» подразумеваются программные ошибки, пользовательские ошибки и ошибки времени выполнения. Прежде чем погружаться в детали, давайте посмотрим, какую роль играет структурированная обработка исключений, и чем она отличается от традиционных методик обработки ошибок.

Роль обработки исключений в .NET

До появления .NET обработка ошибок в среде операционной системы Windows представляла собой весьма запутанную смесь технологий. Многие программисты включали собственную логику обработки ошибок в контекст интересующего приложения. Например, команда разработчиков могла определять набор числовых констант для представления известных сбойных ситуаций и затем применять эти константы в качестве возвращаемых значений методов.

Помимо приемов, изобретаемых самими разработчиками, в API-интерфейсе Windows определены сотни кодов ошибок с помощью #define и HRESULT, а также множество вариаций простых булевских значений (bool, BOOL, VARIANT BOOL и т.д.). Более того, многие разработчики СОМ-приложений на языке С++ (а также VB 6) явно или неявно применяют небольшой набор стандартных СОМ-интерфейсов (наподобие ISupportErrorlnfo. IErrorlnfo или ICreateErrorlnfо) для возврата СОМ-клиенту понятной информации об ошибках.

Очевидная проблема со всеми этими более старыми методиками — отсутствие симметрии. Каждая из них более-менее вписывается в рамки какой-то одной технологии, одного языка и, пожалуй, даже одного проекта. В .NET поддерживается стандартная методика для генерации и выявления ошибок в исполняющей среде, называемая .

Прелесть этой методики состоит в том, что она позволяет разработчикам использовать в области обработки ошибок унифицированный подход, который является общим для всех языков, ориентированных на платформу .NET. Благодаря этому, программист на C# может обрабатывать ошибки почти таким же с синтаксической точки зрения образом, как и программист на VB и программист на С++, использующий C++/CLI.

Дополнительное преимущество состоит в том, что синтаксис, который требуется применять для генерации и перехвата исключений за пределами сборок и машин, тоже выглядит идентично. Например, при написании на C# службы Windows Communication Foundation (WCF) генерировать исключение SOAP для удаленного вызывающего кода можно с использованием тех же ключевых слов, которые применяются для генерации исключения внутри методов в одном и том же приложении.

Еще одно преимущество механизма исключений .NET состоит в том, что в отличие от запутанных числовых значений, просто обозначающих текущую проблему, они представляют собой объекты, в которых содержится читабельное описание проблемы, а также детальный снимок стека вызовов на момент, когда изначально возникло исключение. Более того, конечному пользователю можно предоставлять справочную ссылку, которая указывает на определенный URL-адрес с описанием деталей ошибки, а также специальные данные, определенные программистом.

Составляющие процесса обработки исключений в .NET

Программирование со структурированной обработкой исключений подразумевает использование четырех следующих связанных между собой сущностей:

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *