Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать

Исключительные ситуации как объекты

В объектно-ориентированном языке программирования Object Pascal исключительным ситуациям соответствуют объекты. Эти объекты приложение получает чтобы, используя их, обработать ИС. Тип исключительной ситуации объявляется как любой другой класс, и именно имена этих классов указываются после директивы on. Обычно эти классы являются потомками класса Exception, определенного в модуле SysUtils. В модуле SysUtils описано большое число потомков класса Exception, соответствующих часто встречающимся случаям ошибок. Содержащиеся в SysUtils.pas классы ИС представляют собой достаточно разветвленное иерархическое дерево. Например, классы исключительных ситуаций для ошибок вычислений:

Type EExternal = class(Exception); //ошибка вычислений с плавающей точкой EMathError = class(EExternal); //параметр за пределами допустимого диапазона EInvalidArgument = class(EMathError); //неизвестная инструкция процессора EInvalidOp = class(EMathError); //попытка деления на 0 при вычислениях с плавающей точкой EZeroDivide = class(EMathError); //переполнение при вычислениях с плавающей точкой EOverflow = class(EMathError); //исчезновение порядка при вычислениях с плавающей точкой EUnderflow = class(EMathError);

Классы Exception и его потомки, в отличие от прочих классов, именуются не с буквой Т, а с буквой Е в начале.

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

type EInOutError = class(Exception) ... ErrorCode: integer; end;

Для создания объекта ИС, вызывается конструктор соответствующего класса после директивы raise. Например:

raise EMathError.Create(...);

У классов ИС имеется, как правило, более чем по одному конструктору. Различаются они названиями и параметрами. В качестве параметров обычно передается строка, содержащая описание ИС. Также могут передаваться ссылки на ресурсы приложения, ссылки на справку и т.п.

Если в обработчике ИС директива raise указана без имени класса ИС и вызова конструктора, вторично порождается та же ИС, которая будет обработана во внешнем по отношению к текущему блоке.

Созданный объект ИС уничтожается автоматически после того, как ИС будет обработана. Удалять его вручную не следует.

Обычно нет необходимости программно создавать объекты ИС – они создаются автоматически при возникновении соответствующих исключительных ситуаций (например, внутри классов библиотеки Borland Delphi при наличии проблем). Но иногда такая необходимость возникает. Например, при проверке какого-либо условия выясняется, что дальнейшие действия не могут быть корректно выполнены – тогда ИС порождается программно. После выполнения этого действия управление передается соответствующему блоку обработки ИС, откуда далее оно не возвращается к оператору, следующему за raise, а переходит за пределы блока try...except или try…finally.

Для примера опишем функцию, которая преобразует строку в число с заданным диапазоном и при выходе за пределы этого диапазона порождает ИС:

function StrToIntRange(const S: string; Min, Max: longint): longint; begin Result := StrToInt(S); //преобразуем строку в число //проверяем принадлежность заданному диапазону if (Result < Min) or (Result > Max) then //если за пределами, порождаем ИС raise ERangeError.CreateFmt( '%d is not within the valid range of %d..%d', [Result, Min, Max]); end;

Порожденная таким образом ИС будет поймана блоком обработки, внутри которого произошел вызов этой функции. По завершении обработки созданный объект ИС будет автоматически удален.

Самое важное в объекте ИС – это то, к какому классу он принадлежит. Именно по этому классу будет выбран соответствующий обработчик ИС, и именно имя класса указывается после директивы on. В примере:

try X := Y/Z; except on EZeroDivide do HandleZeroDivide; end;

EZeroDivide – имя класса исключительной ситуации.

При поиске нужного обработчика последовательно проверяется совместимость типа объекта ИС с классами, указанными после директив on. При этом соблюдаются правила проверки на принадлежность объекта классу: для объекта класса-потомка имеет место принадлежность классу-предку. И, если обработчик класса-предка стоит перед обработчиком класса-потомка, последний никогда не получит управления. Например:

try i := 1; j := 0; k := i div j; ... except on EIntError do ShowMessage(‘Ошибка вычислений’); on EDivByZero do ShowMessge(‘Деление на 0’); end;

Класс EIntError описывает любые ошибки целочисленных вычислений. Класс EDivBzZero соответствует ошибке целочисленного деления на 0 и является потомком класса EIntError. И хотя в этом примере имеет место деление на 0, появится сообщение о какой-то ошибке вычислений (EIntError). Если поменять две конструкции on...do местами, то все придет в норму.

Хотя самым главным в объекте ИС является его тип, но поля и свойства также полезны, поскольку они несут дополнительную информацию. Например, свойство message представляет собой строку, содержащую информацию об ИС. Для того чтобы получить доступ к этой информации, надо иметь доступ к объекту ИС. Для этого объект надо поименовать внутри on...do, указав перед именем класса некий идентификатор. Например:

Try ... except on EZD: EzeroDivide do EZD.Message := EZD.Message + ’ Печально, но это действительно ошибка!’; raise; End;

В этом примере к строке Message объекта EZD, содержащей сообщение об ошибке, добавится фраза ‘Печально, но это действительно ошибка!’. И обработка будет передана во внешний блок. Который, скорее всего, просто выведет на экран уже модифицированное сообщение об ошибке.

Понятно, что использовать такой способ именования объекта ИС можно, только если есть директивы on..do. Но в блоке try...finally, также как и в блоке try...except без on…do или после else, работать с объектом ИС таким образом не представляется возможным. А иногда это бывает необходимо. Например, когда возникает необходимость записать в файл всю информацию о любой ИС. В этом случае можно использовать системную функцию ExceptObject, которая вернет указатель на объект, соответствующий текущей исключительной ситуации.

    1. Обработка исключений в С++

Язык C++ поддерживает два механизма обработки исключений:

  • С++ исключения (С++ exceptions);

  • структурированную обработку исключений (structured exception handling) – характерна для С, для С++ в общем случае не рекомендуется, но иногда требуется.

Структурированная обработка исключений обеспечивается взаимодействием ОС Windows 95 или Windows NT и механизма языка программирования.

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

Блок try-except:

__try составной оператор __except (выражение) составной оператор

Составной оператор после __try представляет собой тело блока, для которого будут обрабатываться исключения. Составной оператор после __except представляет собой обработчик исключений. Процесс выполнения операторов следующий:

  • выполняются операторы после __try;

  • если не возникло исключений, управление передается первому оператору после блока, следующего за __except;

  • если исключение возникло во время выполнения операторов в секции после __try (в том числе и в вызываемых функциях), вычисляется значение выражения после __except, и, в зависимости от его значения, производится обработка исключения.

Возможны три значения выражения после __except:

EXCEPTION_CONTINUE_EXECUTION (–1) 

 - исключение отклонено. Выполнение продолжается после точки, где возникло исключение.

EXCEPTION_CONTINUE_SEARCH (0)  

 - исключение не опознано. Продолжается поиск соответствующего обработчика во внешних блоках try-except.

EXCEPTION_EXECUTE_HANDLER (1)  

 - исключение опознано. Управление передается составному оператору после __except, далее действия выполняются в соответствии с содержащимися в нем инструкциями.

Поскольку выражение после __except вычисляется как C выражение, это может быть одиночное значение, оператор условия, или оператор « , ». Если нужны более сложные вычисления, выражение может вызывать функцию, которая должна возвращать значение, равное одному из трех вариантов, перечисленных ранее.

Структурированная обработка исключений предоставляет два встроенных средства, которые доступны в блоке try-except. Это функции _exception_code и _exception_info.

Функция _exception_code возвращает код исключения (32-bit integer). Обычно именно по значению, возвращаемому этой функцией, принимается решение об обработке исключения – либо путем сравнения с константой, либо фильтром внутри вызываемой функции.

Функция _exception_info возвращает указатель на структуру, содержащую дополнительную информацию об исключении.

Блок try-finally:

__try блок операторов __finally блок операторов

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

Рассмотрим пример:

#include "stdio.h"

void main() { int* p = 0x00000000; //пустой указатель puts("начало"); try{ puts("в блоке try"); try{ puts("в блоке try"); *p = 13; //вызовет ИС ошибки доступа }__finally { puts("в блоке finally"); } }__except(puts("проверка условия "), 1) { puts("в блоке except"); } puts("конец"); }

В результате будет выведено:

Начало в блоке try //зашли во внешний защищенный блок try-except в блоке try //зашли во вложенный блок try-finally

проверка условия //выполняется проверка условия; //возвращена 1 – исключение опознано в блоке finally //вернулись во вложенный finally в блоке except //переход в соответствующий обработчик ИС конец

С++ исключения

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

Возникновение ИС анализируется в пределах защищенного блока (guarded block). Синтаксис защищенного блока:

try составной оператор catch (объявление исключения) составной оператор ... catch (объявление исключения) составной оператор

Защищенные блоки можно вкладывать один в другой.

Синтаксис возбуждения исключения:

throw <выражение>

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

throw “Error”;

Ключевое слово throw без последующего «выражения» может использоваться только в обработчиках исключений для организации обработки текущего исключения в следующем по иерархии вызова защищенном блоке.

Заключенное в круглые скобки объявление исключения после catch определяет тип данных, описывающих обрабатываемое исключение. Причем тип данных может быть любым допустимым в языке типом, включая класс. Для каждого исключения, которое может сгенерировать программа, должен быть предусмотрен свой обработчик. Обработчики исключений просматриваются по порядку, и выбирается обработчик исключения, тип аргумента которого соответствует типу исключения (с учетом наследования классов). Обработчик, объявленный как

catch(...)

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

Рассмотрим последовательность действий при возникновении исключительной ситуации в защищенном блоке:

1. Программа ищет ближайший подходящий обработчик исключения. Ближайшим является тот, в чей защищенный блок исполнение попало в последнюю очередь. А для обработчиков одного блока – в порядке описания.

2. Если обработчик найден, стек очищается (сворачивается), причем автоматически вызываются деструкторы всех созданных на стеке объектов, и управление передается обработчику исключения.

3. Если обработчик не найден ни в одном из вложенных блоков, вызывается функция terminate для завершения приложения.

При отсутствии в теле обработчика операторов goto, выполнение программы будет возобновлено, начиная с точки, следующей за последним обработчиком исключений данного блока try. Управление в блок try не возвращается ни в каком случае.

Пример:

try { //Любой код, который может сгенерировать исключение } catch (Т X) { //Обработчик исключения Х типа Т, которое могло быть //ранее сгенерировано внутри предыдущего блока try } //Обработчики исключений блока try catch (...) { //Обработчик любого исключения блока try }

Есть возможность переопределить функцию terminate, которая вызывается, когда исключение не было обработано. Эта функция не имеет аргументов и возвращаемого значения. Для того, чтобы эта функция стала вызываться вместо стандартной используется функция set_termination. Она возвращает указатель на старую функцию terminate, которую можно вызывать в своей.

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

try { CMyEx e(“Exception”,15); //статический объект ИС throw e; } catch (CMyEx& ex) { //создается временная копия объекта в стеке, //которая уничтожится автоматически }

try { CException *e = new CException; //динамический объект ИС throw e; } catch (CException *ex) { //память под объект ИС распределяется динамически //после обработки объект нужно уничтожить delete ex; //или передать дальше с помощью throw }

Объекты стандартных библиотек, таких как MFC Microsoft Visual C++ или VCL Borland C++ Builder, генерируют собственные исключения типа класс. Также можно генерировать собственные исключения собственных типов и их обрабатывать.

Третья возможность возникновения С++ исключения – структурированные исключения С, генерируемые из-за программных или аппаратных проблем. Они обрабатываются обработчиком исключений С++ с многоточием:

catch(...)

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