Обработка исключительных ситуаций
В языке С++ имеется возможность обработки исключительных ситуаций. Под исключительной ситуацией понимается возникновение редко случающихся событий. Например, в программе с помощью оператора new выделяется динамическая память. В обычной ситуации оператор new отрабатывает штатно, и динамическая память в куче выделяется, но может наступить такой момент, когда операционная система не сможет выделить ещё один кусок памяти. Такие ситуации программист должен отслеживать, если хочет, чтобы его программа работала устойчиво и без сбоев или, если сбой произошёл, то хотя бы быстро определить место в программе, которое его вызвало, чтобы было легче исправить ошибку.
Какие возможности имеются у программиста для отслеживания исключительных ситуаций? Во-первых, библиотечные функции языка С и С++ могут возвращать в точку вызова код ошибки, либо с помощью return, либо через формальный параметр функции или глобальную переменную. Это не очень удобно, так как информация об ошибке может передаваться по-разному, представлена малоинформативным кодом и может быть проигнорирована программистом. Данный способ используется функцией, если возникновение в ней ошибки не является критичной для работоспособности программы и, следовательно, если программист забудет её обработать, то ничего страшного не произойдёт. Программа не зависнет, и операционная систем продолжит свою работу, даже не узнав, что в приложении произошла ошибка. Вероятно, эту ошибку будет трудно обнаружить, поэтому программисты чаще используют второй более современный способ, который используется и для обнаружения критичных (грубых) ошибок.
Второй способ предполагает, что в случаи возникновения исключительной ситуации в теле библиотечной функции генерируется объект (исключение), которой необходимо перехватить и обработать. Если исключение не обрабатывается программистом, то оно обрабатывает операционной системой, которая корректно завершает приложение. Синтаксис генерации и перехвата исключений следующий.
try { ... } – блок, в пределах которого исключение будет перехвачено и направлено обработчикам.
catch (тип1 фор_парам) { ... } [
catch (тип2 фор_парам) { ... } ... ]
один или несколько обработчиков исключительных ситуаций; выбор обработчика (т.е. какой конкрето обработчик перехватит сообщение) зависит от того на сколько тип генерируемого исключения соответствует типу формального параметра обработчика. Если типы не совпадают, то исключение обработано не будет. Важен также порядок следования обработчиков в пограмме. Так как проверка соответствия типов происходит сверху-вниз. Именно поэтому обработчики исключений производного типа должны стоять по тексту программы выше чем обработчики исключений базового класса, так как обработчики исключений базавого класса (в соответствии с правилами языка С++) могут перехватывать исключения производного типа.
В языке С++ есть обработчик, который перехватит все исключения в независимости от его типа. Его синтаксии следующий: catch (…) { ... }
throw тип(список фактических параметров); оператор генерации исключения заданного типа. Фактически вызывается конструктор, который создаёт объект заданного типа. Тип исключения (с помощью class или struct) программист может определить самостоятельно или воспользоваться уже имеющимися в библиотеке.
Иерархия стандартных исключений.
Упрощённый вариант определения базового класса exception приведён ниже. Две основные функции этого класса: конструктор с одним параметром exception(“Сообщение об ошибке”) и функция what(), которая возвращает это сообщение в обработчике.
class exception{ // base of all library exceptions
public:
explicit exception(const char *_Message = "unknown"):_Ptr(_Message) {}
virtual const char * what() const
{ // return pointer to message string
return (_Ptr != 0 ? _Ptr : "unknown exception");
}
protected: const char *_Ptr;
…
};
Разберём на конкретных примерах как генерируется, перехватывается и обрабатывается исключение.
Например, оператор new в случае ошибки (не выделение памяти, скорее всего, означает неправильное освобождение динамической памяти в программе -утечку памяти) генерирует стандартное исключение типа bad_alloc. Следовательно, при возникновении этой ошибки придётся ещё раз просмотреть всю программу на предмет правильного выделения и освобождения динамической памяти, но, по крайней мере, мы будем знать, что программный сбой связан с динамической памятью.
#include "stdafx.h"
#include <new>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int i = 0;
int *p;
try{//Бесконечный цикл для выделения (и утечки) динамической памяти
while (1) {cerr<<"\n i= "<<i++; p = new int[10000000];cerr<<hex<<p;}
}
catch(std::bad_alloc x) {
cerr<<"\n Processing of exception = "<<x.what();
}
cerr<<"\nEnd\n";
return 0;
}
В программе организован бесконечный цикл, в котором выделяется динамически память. Понятно, что в какой-то момент времени память закончиться, и оператор new сгенерирует стандартное исключение типа bad_alloc, которое мы перехватываем и обрабатываем.
Ниже приведён пример использования своего собственного класса для генерации исключения.
#include "stdafx.h"
#include <new>
#include <iostream>
class except{//1 Класс, объекты которого будем генерировать в качестве исключения.
int CodError; //2
public: //3
except(int i){CodError = i;} //4
virtual int what(){return CodError;} //5
}; //6
void f1(int i){ //7
cout<<"\nf1()"; //8
if(i==1)throw except(i); //9 Генерация исключения
}//10
void f2(int i){ //11
cout<<"\nf2()"; //12
f1(i); //13
}//14
int main(int argc, char* argv[]) //15
{ //16
try{//17 Блок, в пределах которого перехватывается исключение
f2(0); //18
f2(1); //19
f2(0); //20
} //21
catch(except x){//22 Обработчик исключения
cout<<"\n processing except x.CodError = "<<x.what(); //23
} //24
}
Для этого самостоятельно определим класс except (строки 1…6), объекты этого класса будут генерироваться при возникновении исключения. Поскольку класс мы пишем сами, то мы можем включить в него любые компонентные данные и функции, но, чтобы не загромождать пример пусть их количество будет минимальным. Тем не менее, важно, что при желании в любой момент мы можем добавить функциональности нашему классу и тем самым включить в генерируемый объект больше информации о причинах или месте возникновения ошибки.
В теле функции f1() в строке 9 с помощью ключевого слова throw и конструктора except(i) генерируется объект типа except.
В теле функции main в строках с 17 по 21 мы, используя ключевое слово try, выделяем блок операторов, в которых, по нашему мнению, может возникнуть исключение, которое мы хотим перехватить.
Дата добавления: 2020-12-11; просмотров: 383;