Несмотря на то что Переменная ProblemSolver представляет собой указатель на объект defect_response, полиморфизм позволяет этой переменной указывать на объект типа exception_response или любой другой объект, выведенный из класса defect_response. Поскольку метод doSomething () объявлен виртуальным в классе defect_response, компилятор может выполнить динамическое связывание. Это дает гарантию корректного вызова метода doSomething() при выполнении приложения. Именно динамическое связывание позволяет каждому потомку класса defect_response определить собственный метод doSomething (). Нам нужно, чтобы вызов метода doSomething() зависел от того, ссылка на какой именно потомок класса defect_response используется при этом. Рассматриваемый метод позволяет связывать номера ошибок с объектами, имеющими отношение к обработке определенных сбойных ситуаций. С помощью этого метода можно значительно упростить код обработки ошибок. В листинге 7.1, например, показано, как значение, возвращаемое некоторой функцией, можно использовать для выбора соответствующего объекта обработки ошибок.

// Листинг 7.1. Использование значений, возвращаемых

// функцией, для определения корректного

// объекта типа ErrorHandler

void importantOperation(void) {

//. . .

Result = reliableOperation(); if(Result != Success){

defect_response *Solver;

Solver = ErrorTable[Result];

Solver->doSomething();

}

else{

// Продолжение обработки.

}

// . . .

}

В листинге 7.1 обратите внимание на то, что мы не используем последовательность if- или case- инструкций. Объект отображения позволяет получить непосредственный доступ к желаемому объекту обработки ошибок по индексу. Конкретный метод doSomething(), вызываемый в листинге 7.1, зависит от значения переменной Result. Безусловно, данный пример демонстрирует упрощенную схему обработки ошибочных ситуаций. Так, например, в листинге 7.1 не показано, кто (или что) отвечает за управление динамически выделяемой памятью для объектов, хранимых в отображении ErrorTable. Кроме того, здесь не учтено, что функции reliableOperation() и doSomething() могут выполниться неудачно. Поэтому реальный код будет, конечно же, несколько сложнее, чем тот, что приведен в листингe 7.1. Но все же этот пример ясно показывает, как одним «ударом» обработать множество ситуаций сбоя. Мы можем пойти еще дальше. В листинге 7.1 предполагается, что все возможные ошибки будут охвачены объектами типа ErrorTable. Все ErrorTable-объекты представляют собой либо объекты типа defect_response, либо объекты, выведенные из класса defect_response. А что, если у нас будет несколько семейств классов обработки ошибок? В листинге 7.2 показано, как с помощью шаблонов сделать функцию importantOperation () более общей.

// Листинг 7.2. Использование шаблона в функции // importantOperation()

template<class T,class U> int importantOperation(void) {

T ErrorTable; //.. .

U *Solver; //...

Solver = ErrorTable[Result]; Solver->doSomething () ; //...

};

В листинге 7.2 тип ErrorTable не ограничен объекта м и класса defect_response. Этот метод позволяет упростить код обработки ошибок и повысить его гибкость. Здесь демонстрируется использование полиморфизма как по вертикали, так и по горизонтали, что чрезвычайно важно для SPMD- и MPMD-программ. Как упростить программы, реализующие параллелизм с помощью шаблонов и полиморфизма, описано в главе 9. Использование объектов отображения и объектов обработки ошибок — это важнал составляющал повышения надежности ПО. Помимо методов обработки ошибок, мы можем также воспользоваться преимуществами механизма обработки исключительных ситуаций и классов исключений, прелусмотренных в С++ (этому посвящен следующий раздел).

Механизмы обработки исключительных ситуаций в С++

В идеале во время тестирования и отладки должны быть ликвидированы все дефекты протраммы или по крайней мере максимально возможное их количество. Кроме того, следует обработать нежелательные и неудобные условия с использованием обычной программной логики. После устранения всех (или почти всех) дефектов и обработки нежелательных и неудобных условий все остальные «неприятности» попадают в разряд исключительных ситуаций. Обработка исключительных ситуаций в С++ по д держивается с помощью трех ключевых слов: try, throw и catch. Любой код, сталкивающийся сисключительной ситуацией, с которой он не в силах справиться самостоятельно, генерирует исключение «в надежде» на то, что с ней совладает некоторый другой обработчик (расположенный где-то в другом месте программы) (Б. Страуструп, Язык программирования С++ , 1997). Для генерирования объекта некоторого специального типа (типа исключения) используется ключевое слово throw. При этом происходит передача управления обработчику исключения, который предназначен д л я обработки объектов данного типа. Д л я идентификации обработчиков, предназначенных д л я перехвата объектов исключений, используется ключевое слово catch. Рассмотрим пример.

void importantOperation {

/ / executeImportCode ()

// Возникает исключительная ситуация.

impossible_condition ImpossibleCondition;

throw ImpossibleCondition;

//...

}

catch (impossible_condition &E) {

// Выполнение действий, связанный с объектом E.

//...

}

Функция importantOperation( ) пытается выполнить свою работу и сталкивается с необычными условиями, с которыми она не в состоянии справиться. В нашем примере она создает объект типа impossible_condition и использует ключевое слово throw для генерирования этого объекта. Блок кода, в котором используется ключевое слово catch, предназначен для перехвата объектов типа impossible_condition. Этот блок кода называется обработчиком исключений. Обработчики исключений связаны с блоками кода, поме щ енными в try -выражения. Назначение try -блоков — обозначить область, в которой возможно возникновение исключительной ситуации. Блок catch должен сразу же следовать за соответствующим try -блоком или другим catch -блоком. Вот пример:

try{

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату