Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
274
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

810

Часть IV # Расширенное использование C-^-f

inline void fraction (long

numer,

long denom, double&

result)

{

inverse(denom,

result);

 

/ /

result = 1.0 / denom

 

result = numer *

result;

}

/ /

result = numer/denom

int mainO

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

while

(true)

 

 

 

 

 

 

 

{ long numer, denom; double ans;

 

cout

«

MSG::msg(3)

«

MSG:; msg(4);

i f

((cin

»

numer »

denom)

== 0) break;

try {

 

fraction

(numer,

denom, ans);

 

 

 

 

}

cout «

 

MSG::msg(5)

«

ans

« " \ n \ n "

 

 

 

 

 

 

 

 

 

 

 

catch

 

(char*

str)

 

 

 

 

 

{

cout

«

str;

}

 

 

 

 

 

catch

 

(long

val)

 

 

 

 

 

{

cout

«

MSG::msg(2)«

val

«

" \ n \ n " ; }

}

 

 

 

 

 

 

 

 

 

 

 

return

0;

 

 

 

 

 

 

 

 

/ /

числитель/знаменатель

/ /

запрос данных от пользователя

/ /

ввод данных

/ /

вычисление ответа

/ /

действительный ответ

/ /

нулевой знаменатель

/ /

отрицательное значение

Если inverseO не сгенерировала исключительные ситуации, то fractionO и mainO продолжают вычисление и вывод результата и запрашивают следуюп^ий набор данных. Если inverseO генерирует исключительную ситуацию, она не обра­ батывается в inverseO, потому что в ней отсутствуют соответствующие струк­ туры catch. Поиск распространяется на fractionO. Поскольку fractionO не имеет каких-либо программ обработки исключительных ситуаций, поиск ведется в main(). Если в main() также отсутствует какие-либо программы обработки исключительных ситуаций, выполнение программы завершается.

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

В этом примере блок try составляется из двух операторов: вызова fractionO и оператора вывода. Что произойдет, если переместить вызов fractionO за пре­

делы блока

try?

 

 

 

int

mainO

 

 

 

 

 

{ while (true)

 

 

 

// числитель/знаменатель

{

long

numer,

denom; double ans;

 

cout

«

MSG::msg(3«

MSG::msg(4);

// запрос данных отпользователя

 

if ((cin »

numer »

denom) == 0) break;

// ввод данных

 

fraction(numer,denom,ans);

// вычисление ответа

 

try {

 

 

MSG::msg(5) «

ans «"\n\n"; }

// действительный ответ

 

cout «

 

catch

(char* str)

 

 

// нулевой знаменатель

 

{ cout « str; }

 

 

// отрицательное значение

 

catch

(long val)

 

val « "\n\n"; }

 

{ cout « MSG::msg(2) «

// конец цикла

 

return 0; }

 

 

 

 

Глава 18 • Программирование с обработкой исключительных ситуаций

| 811 1

В этой структуре не сделано главное. Оператор try не будет создавать какиелибо исключительные ситуации. А блоки catch могут обработать только исклю­ чительные ситуации, происходящие из предшествующего оператора try. Когда inverseO генерирует исключительную ситуацию для fraction(), а fraction() генерирует эту исключительную ситуацию для main(), то ни один блок catch не будет обрабатывать исключительные ситуации, и выполнение программы завер­ шится.

А что, если в блок try поместить только вызов функции, оставляя оператор вывода за пределами? Основной повод для этого состоит в том, что поскольку оператор не генерирует никакие исключительные операции, он портит драгоцен­ ное пространство в блоке t гу.

int

mainO

 

 

 

 

 

 

 

 

 

{ while

(true)

 

 

 

 

 

 

 

{

long

numer,

denom;

double ans;

 

 

/ /

числитель/знаменатель

 

cout

«

MSG: :msg(3)

«

MSG: :msg(4);

/ /

запрос данных от пользователя

 

i f

((cin

»

numer »

denom) == 0) break;

 

/ /

ввод данных

 

t r y

{

 

 

 

 

 

 

 

 

 

 

 

fraction(numer,denom,ans);

}

 

/ /

вычисление ответа

 

cout

«

MSG::msg(5)

«

ans «

" \ n \ n " ;

 

/ /

действительный ответ

 

catch

(char*

str)

 

 

 

 

/ /

нулевой знаменатель

 

{

cout

«

str; }

 

 

 

 

 

 

 

catch

(long

val)

 

 

 

 

/ /

отрицательное значение

 

{

cout

«

MSG: :msg(2) «

val «

" \ n \ n " ; }

}

/ /

конец цикла

 

return

0; }

 

 

 

 

 

 

 

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

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

int

mainO

 

 

 

 

 

 

 

 

 

 

{ while

(true)

 

 

 

 

 

 

 

 

{

long

numer,

denom; double

ans;

 

 

/ /

числитель/знаменатель

 

t r y

{

 

 

 

 

 

 

 

 

 

 

 

cout

«

MSG: :msg(3)

«

MSG: :msg(4);

/ /

запрос данных от пользователя

 

i f

((cin

»

numer »

denom)

== 0) break;

/ /

ввод данных

 

fraction(numer,denom,ans);

 

 

/ /

вычисление ответа

 

cout

«

MSG: :msg(5)

«

ans « " \ n \ n " ;

}

/ /

конец t r y

 

catch

(char* str)

 

 

 

 

/ /

нулевой знаменатель

 

{ cout

«

str;

}

 

 

 

 

 

 

 

catch

(long

val)

 

 

 

 

/ /

отрицательное значение

 

{ cout

«

MSG::msg(2) «

val «

" \ n \ n " ;

} }

/ /

конец цикла

 

return

0; }

 

 

 

 

 

 

 

 

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

Что можно сказать в отношении помеш^ения всего цикла while в оператор try? Все будет зависеть от того, как это будет сделано. Если поместить зарезервиро­ ванное слово try после открываюш^ей скобки и оставить закрывающую скобку на своем месте, компилятору это не понравится.

812

Часть IV » Расширенное использование C+'f

 

 

int mainO

 

 

 

 

{ try {

 

 

 

 

while (true)

 

 

// числитель/знаменатель

 

{ long numer, denom; double ans;

 

 

cout « MSG::msg(3) «

MSG: :msg(4);

 

// запрос данных отпользователя

 

if ((cin » numer » denom) == 0) break;

// ввод данных

 

fraction(numer,denom,ans);

}

// вычисление ответа

 

cout » MSG: :msg(5) «

ans «"\n\n";

// конец блока try

 

catch (char* str)

 

 

// нулевой знаменатель

 

{ cout « str; }

 

 

// отрицательное значение

 

catch (long val)

val « "\n\n";

} }

 

{ cout « MSG::msg(2) «

// конец цикла

 

return 0; }

 

 

 

Теперь область видимости оператора try не является вложенной в область действия цикла while. Какое бы проектное решение ни было принято, области видимости должны быть вложены корректно. В ином случае компилятор станет в тупик. Чем уже область видимости оператора try, тем лучше.

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

Где сгенерировать исключительную ситуацию

Где отследить исключительную ситуацию

Какую информацию отправить программе обработки исключительной ситуации

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

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

Обозначение исключительной ситуации

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

Зарезервированное слово throw используется при обозначении исключитель­ ных ситуаций. Его обш,ая синтаксическая форма объединяет обычное объявление функции, зарезервированное слово throw и список типов, значения которых гене­ рируются функцией при поиске программы обработки исключительной ситуации.

functionDeclaration throw (Typel, Туре2, ... TypeN);

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

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

Глава 18 • Програмгугирование с обробошой искдючитедьных ситуаций

Г^1Г1

Например, функция inverseO в листинге 18.4 генерирует (и не отслеживает) две исключительные ситуации явно — символьный массив и long. Определение этой функции должно включать зарезервированное слово throw с такими двумя типами.

i n l i ne void

inverse(long

value,

clouble& answer)

 

 

 

throw

(char*,

long)

 

 

{ answer = (value)

? 1.0/value

: DBL_MAX;

 

i f

(answer==DBL_MAX)

 

 

 

 

throw

MSG::msg(1);

 

/ /

явный throw

i f

(value

< 0)

 

 

 

 

 

throw

value;

}

 

/ /

явный throw

Подобным же образом, функция f raction() в листинге 18.4 не генерирует какихлибо явных исключительных ситуаций, но ее серверная функция inverse() генери­ рует (и не отслеживает) две исключительные ситуации. То есть функция f raction() генерирует две исключительные ситуации неявно и должна обозначить их обе.

inline void fraction (long numer. long denom,

double&

result)

throw

(char*,

long)

 

 

{ inverse(denom,

result);

 

/ /

неявный throw

result = numer

* result;

}

/ /

result = numer/denom

Если функция не генерирует исключительные ситуации, она может быть объ­ явлена с пустой спецификацией throw(). Например:

void foo() throw (); / / ожидается отсутствие исключительных ситуаций

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

void foo(); / / throw отсутствует: ожидаются любые исключительные ситуации

 

 

 

Хорошо, если бы обозначение исключительной ситуации, которую функция

 

 

фактически не сгенерировала, было бы в С+Н- ошибкой. Также было бы хорошо,

 

 

если бы отсутствие обозначения исключительной ситуации, сгенерированной

 

 

функцией, явно или неявно было бы ошибкой. Однако это не так, и можно исполь­

 

 

зовать обозначения, вводяш,ие в заблуждение (обозначая исключительные ситуа­

 

 

ции, которые функция не генерировала), или несоответствуюш.ие обозначения

 

 

(обозначая только части исключительных ситуаций, которые генерирует функция).

 

 

См. листинг 18.4.

 

 

 

 

Обозначение исключительных ситуаций — мош,ный метод документирования

 

 

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

Enter

numerator

and positive

 

Когда функция обрабатывает исключительные си­

11 0

туации только частично, это отражается в том, как

denominator (any letter toquit):

функция обозначает исключительные ситуации. В лис­

Zero

denominator

isnot

allowed

 

тинге 18.5 показано обозначение исключительных си­

Enter

numerator

and positive

 

туаций для различного разделения обязанностей между

11-11

функциями inverseO и fraction(). Поскольку функ­

denominator (any letter

toquit):

Negative denominator: -11

 

ция inverseO генерирует (и обозначает) те же самые

 

исключительные ситуации, что и в листинге 18.4,

Value of the fraction:

-1

 

функция fraction О сама обрабатывает исключитель­

 

ную ситуацию типа long. Следовательно, она обозна­

Enter

numerator

and positive

 

-11 44

чает только одну исключительную ситуацию в своем

denominator (any

letter toquit):

интерфейсе,— символьный массив.

Value ofthe fraction:

-0.25

 

Enter numerator and positive denominator (any letter toquit): quit

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

Рис, 18.3. Вывод для программы из листинга 18.5

814

Часть IV # Расширенное использование С-^-^

Листинг 18.5. Пример обозначения, генерации и отслеживания исключительных ситуаций

#inclucje <iostream> #inclucle <cfloat> using namespace std;

class MSG

{

 

 

 

];

// внутренние статические данные

 

static char* data [

public-

 

 

 

 

 

// общедоступный статический метод

 

static char* msg(int n)

 

{ if (n<l II n > 5)

 

// проверка допустимости

индекса

 

 

return data[0];

 

 

 

 

else

 

 

 

 

// возвращение допустимой

строки

 

 

return data[n]; }

char* MSG::data

[] = { "\nBad argument tomsg()\n",

 

"\nZero denominator isnot allowed\n\n",

// хранилище текста

 

"\nNegative denominator: ",

 

 

"Enter numerator and positive\n",

 

 

"denominator (any letter toquit): ",

 

 

"Value ofthe fraction: "

 

 

 

} ;

 

 

 

 

 

 

 

inline void inverse(long

value, double& answer)

 

{

 

throw (char*, long)

 

 

answer = (value) ? 1.0/value : DBL_MAX;

 

 

 

if (answer==DBL_MAX)

 

 

 

 

 

throw MSG::msg(l);

 

 

 

 

 

if (value < 0)

 

 

 

 

 

throw value; }

 

 

 

 

inline void fraction (long numer, long denom,

double& result)

 

{ try {

throw (char*)

 

 

 

 

 

 

 

 

// result = 1.0 / denom

 

 

inverse(denom, result); }

 

 

catch (long val)

 

val « "\n\n"; }

// отрицательное значение - OK

 

{ cout «

MSG::msg(2) «

// result = numer / result

 

 

result = numer * result; }

 

int mainO

 

 

 

 

 

 

 

{

while (true)

 

 

 

// числитель / знаменатель

 

 

{ long numer, denom; double ans;

 

 

cout «

MSG::msg(3) «

MSG::msg(4);

// запрос ввода данных от пользователя

 

if ((cin »

numer »

denom) == 0) break;

// ввод данных

 

 

try {

 

 

 

 

 

// вычисление ответа

 

 

fraction(numer,denom,ans);

 

 

cout «

MSG::msg(5) « ans «"\n\n"; }

// допустимый ответ

 

 

catch (char* str)

 

 

// нулевой знаменатель

 

 

{ cout «

str; }

 

 

 

 

 

}

}

 

 

 

 

 

 

 

return 0;

 

 

 

 

 

 

В этом примере показано преимуидество обозначения исключительных ситуа­

ций в интерфейсах функций. Когда программист клиентской части желает знать,

какие исключительные ситуации должны обрабатываться клиентской функцией,

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

Глава 18 • Программирование с обрабошои искАЮчтвАЬиык ситуаций

815

Повторная генерация исключительной ситуации

Обратите внимание на то, что поведение программы, показанное на рис. 18.3, отличается от поведения, представленного на рис. 18.2. На рис. 18.2 отрицатель­ ное значение знаменателя отклоняется, и у пользователя запрашивается новый ввод. На рис. 18.3 отрицательное значение знаменателя отклоняется, но значение результата печатается в любом случае.

Причина этого в том, что функция f ractionO сама осуществляет обработку исключительной ситуации (посредством вывода сообщения и значения знамена­ теля), а функция mainO предполагает, что результат является допустимым, и не подавляет его вывод.

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

Например, функция inverseO может избежать ввода в заблуждение функции mainO, которая предполагает, что она завершила восстановление, генерируя снова исключительную ситуацию.

i n l i ne

void fraction (long numer, long

denom, double& result)

 

 

 

throw (char*,

long)

/ /

обозначение

дополнительной

{ t r y

{

 

 

 

 

 

 

/ /

исключительной ситуации

 

 

 

 

 

 

 

 

 

inverse(denom,

result);

}

/ /

result =1.0

/ denom

catch

(long

val)

 

 

 

 

 

 

 

{

cout

«

MSG::msg(2)

« val «

"\n\n"

 

 

 

 

throw val;

}

 

 

/ /

повторная генерация

result

= numer

*

result;

}

 

 

 

 

Обратите внимание, что не возникает бесконечный цикл. Исключительная си­ туация, сгенерированная в области видимости структуры catch, не может попасть в эту область видимости. Для этого исключительная ситуация должна происходить из блока try, который предшествует структуре catch. Формально исключительная ситуация рассматривается как обработанная при передаче ее программе обработ­ ки исключительной ситуации. Следовательно, этот оператор throw будет осущест­ влять поиск другой программы обработки ошибки long на более высоком уровне в клиентской программе, которая вызвала функцию fraction().

Другой способ повторной генерации исключительной ситуации того же самого типа (и значения) — указать throw в структуре catch, и исключительная ситуация, определенная в параметре структуры catch, будет повторно сгенерирована.

inline void fraction (long numer,

long denom, double& result)

 

 

 

throw (char*,

long)

 

/ /

обозначение

дополнительной

t r y {

 

 

 

 

 

 

 

/ /

исключительной

ситуации

 

 

 

 

 

 

 

 

 

 

 

 

inverse(denom,

result);

}

 

/ /

result

= 1.0

/

denom

catch

(long

val)

 

 

 

 

 

 

 

 

 

{

cout

«

MSG::msg(2)

« val

«

"\n\n";

 

 

 

 

 

throw;

}

 

 

 

 

/ /

то же,

что и "throw val'

result

= numer

*

result;

}

 

 

 

 

 

 

 

В листинге 18.6 представлен этот метод. Функция inverseO та же, что и в лис­ тинге 18.5. Функция f ractionO выполняет частичную обработку исключительной ситуации long, но затем генерирует ее снова. FractionO должна затребовать эту исключительную ситуацию в своем интерфейсе, а main() должна обеспечить

816

Чость IV • Расширенное использование С-^Ф

Enter numerator and positive denominator (any letter to quit): 11 0

Zero denominator is not allowed

Enter numerator and positive

denominator (any letter to quit): 11 -11

Negative denominator: -11

Enter numerator and positive

denominator (any letter to quit): -11 44

Value of the fraction:

-0.25

Enter numerator and positive denominator (any letter to quit): quit

Рис. 18.4. Вывод для программы из листинга 18.6

оператор catch для обработки исключительной ситуа­ ции. Если main О не в состоянии сделать это, програм­ ма завершается аварийно.

Единственная цель повторной генерации данной исключительной ситуации — избежать отображения результата в main(). Следовательно, отсутствует об­ работка, которую структура catch должна выполнить в mainO. Именно поэтому тело блока catch — пустое. Оно все еще должно находиться здесь. Чтобы избе­ жать генерации предупреждения, что параметр струк­ туры catch не используется, он был пропущен в списке параметров и оставлен только тип значения. Это допу­ стимый метод С4- + , хотя и немного громоздкий.

Вывод для программы показан на рис. 18.4. Видно, что посторонний вывод подавляется.

Листинг 18.6. Пример повторной генерации исключительной ситуации в структуре catch

#inclucle <iostreafn> #inclucie <cfloat> using namespace std;

c l a ss MSG {

 

// внутренние статические данные

 

static char* data [];

 

public:

 

// общедоступный статический метод

 

static char* msg(int n)

 

{ if (n<l II n > 5)

 

// проверка допустимости

индекса

 

return data[0];

 

 

 

 

 

else

 

// возвращение допустимой

строки

 

return data[n]; }

 

} ;

 

 

 

 

char* MSG::data [] = { "\nBad argument tomsg()\n",

 

 

"\nZero denominator isnot allowed\n\n",

// хранилище текста

 

 

"\nNegative denominator: ",

 

 

 

 

"Enter numerator and positive\n",

 

 

 

"denominator (any letter toquit): ",

 

 

 

"Value ofthe fraction: "

 

 

 

 

} ;

 

 

 

 

inline void inverse(long value, double& answer)

 

 

{

throw (char*, long)

 

 

 

answer = (value) ? 1.0/value : DBL_MAX;

 

 

 

 

if (answer==DBL_MAX)

 

 

 

 

 

throw MSG::msg(1):

 

 

 

 

 

if (value < 0)

 

 

 

 

 

throw value; }

 

 

 

 

inline void fraction (long numer, long denom, double& result)

 

 

{

throw (char*, long)

 

 

 

try {

}

// result - 1.0/denom

 

 

 

inverse(denom, result);

-

OK

 

catch (long val)

 

// отрицательное значение

 

{ cout « MSG::msg(2) «

val « "\n\n";

 

 

 

throw val; }

 

// result = numer / denom

 

 

 

result = numer * result; }

 

 

 

Глава 18 • Программирование с обработкой исключительных ситуаций

817

int mainO

endl « endl;

 

 

 

{ cout «

 

 

 

while (true)

 

 

 

{ long numer, denom; double ans;

/ /

числитель / знаменатель

 

cout «

MSG::msg(3) « MSG: :msg(4) ;

/ /

запрос ввода данных пользователем

if ((cin » numer » denom) == 0) break;

/ /

ввод данных

 

try {

 

 

 

 

 

fraction (numer,denom,ans);

/ /

вычисление ответа

 

 

cout « MSG::msg(5) « ans «"\n\n"; }

/ /

действительный ответ

 

catch (char* str)

/ /

нулевой знаменатель

 

{ cout « str; }

 

 

 

catch (long)

/ /

просто тип

 

{

}

 

/ /

empty body

 

}

 

 

 

 

 

return

0;

}

 

 

 

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

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

в приведенных выше примерах операторы throw передают блоку catch управ­ ление, а также значение конкретного типа. Доступ к этому значению можно осу­ ществлять в блоке catch. Такой метод помогает установить связи ме>кду местом обнаружения и местом исправления ошибки.

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

void

foo() throw (char*)

 

 

{ i f ( t e s t K ) )

 

 

 

throw "One bad thing happened";

/ /

одна проблема

else i f

(test2())

 

/ /

другая проблема

throw "Another bad thing happened";

proceed_safely();

}

/ /

все в порядке

void

c l i e n t O

 

 

 

{ t r y

 

 

 

 

 

 

{

fooO;

}

 

/ /

все в порядке

catch(char* msg)

 

 

 

{ cout

«

msg «

endl; } }

/ /

любая из двух проблем

Если поведение программы должно отличаться для разных источников проблем, то этот механизм передачи данных становится слишком ограниченным — блок catch должен проанализировать данные, отправленные оператором throw, и выбрать разные ветви в зависимости от результата. Здесь надо знать цель обработки разных ошибок в различных блоках catch.

818

Часть IV * Расширенное использование C-t-f-

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

C + + разрешает эти проблемы, допуская генерацию составных объектов вмес­ то простых значений встроенных типов.

Синтаксис объектов генерации, обозначения и отслеживания

Генерация объекта исключительной ситуации добавляет новое измерение для программирования на C+ + . Проектировш^ик должен решить, какие элементы данных направляются из места, где ошибка была обнаружена, к месту, где проис­ ходит ее исправление. Для каждой исключительной ситуации создайте класс, объекты которого могут нести необходимые данные от места доступа к данным объекта. Методы этого класса позволят структуре catch иметь соответствуюш,ий доступ к данным объекта.

Например, класс ZeroDenom можно спроектировать для передачи данных о ну­ левом знаменателе. По месту обнаружения ошибки объект такого класса будет со­ здан и передан. Для него требуется только одна часть информации, которая будет одинаковой для всех случаев появления ошибок. Следовательно, класс ZeroDenom должен содержать конструктор по умолчанию. В блоке catch выводится сообш,е- ние. Класс ZeroDenom может предоставить метод print(), который будет вызы­ ваться блоком catch.

class

ZeroDenom {

 

char

*msg;

/ /

данные должны передаваться программе обработки ошибки

public:

 

 

ZeroDenom ()

 

/ / вызывается оператором throw

{ msg = MSG::msg (1);

}

void print 0

const

// вызывается в блоке catch

{cout « msg; }

};

Для использования объектов класса в качестве носителей информации об иск­ лючительной ситуации необходимо пройти те же три этапа:

1)генерация исключительной ситуации

2)отслеживание исключительной ситуации

3)обозначение исключительной ситуации

Функция, обнаруживаюш,ая состояние исключительной ситуации, например inverseO, создает объект этого класса и отправляет его на поиск блока catch.

i f (answer==DBL_MAX)

 

throw ZeroDenomO;

/ / необычный синтаксис

Обратите внимание на синтаксис вызова конструктора по умолчанию с указа­ нием имени класса и двух пустых скобок. В других контекстах (например, создание объекта в операторе new) использование скобок было бы синтаксической ошиб­ кой. В этом контексте синтаксической ошибкой является отсутствие скобок. Если в отношении подобного синтаксиса возникают сомнения, то можно создать объект

Глава 18 • Программирование с обработкой ИСКАЮЧИТВАЬИЫХ ситуаций

819

требуемого типа, а затем сгенерировать его так, как генерируются переменные

встроенных типов.

 

 

i f (answer==DBL_MAX)

 

 

{ ZeroDenom zd; throw zd; }

/ / обычный синтаксис

 

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

class

NegativeDenom {

 

 

 

 

long val;

 

/ / закрытые данные информации исключительной

ситуации

 

char* msg;

 

 

 

 

 

public:

 

 

 

 

 

NegativeDenom(long

value)

/ /

конструктор преобразования

 

 

: val (value), msg (MSG::msg(2)) { }

 

char* getMsg( )

const

 

 

 

{

return msg;

}

 

 

 

 

long getValO const

 

/ /

общедоступный метод доступа

к данным

{

return val;

}

} ;

 

 

 

Чтобы сгенерировать объект этого типа, конструктору требуется сообндить значение параметра при помощи метода, который генерирует объект, например inverse().

i f (value

< 0)

/ /

анализ ситуации

throw

NegativeDenom(value);

/ /

генерация исключительной ситуации

Подобно объектам, не имеющим параметров, этот объект можно создать, вос­ пользовавшись обычным синтаксисом, а затем сгенерировать его.

i f (value < 0)

{ NegativeDenom nd(value); throw nd; }

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

inline void

inverse(long value,

double& answer)

 

 

throw (ZeroDenom, NegativeDenom)

/ /

обозначение исключительной ситуации

{ answer = (value) ? 1.0/value : DBL_MAX;

 

 

i f

(answer==DBL_MAX)

 

 

 

 

 

throw ZeroDenomO;

 

/ /

генерация объекта

класса

i f

(value

< 0)

 

 

 

 

 

throw

NegativeDenom(value);

}

/ /

генерация объекта

класса

Чтобы отследить объект класса, его параметр должна определить структура catch. В рамках области видимости структуры catch правила доступа к объекту те же, что и для объектов любого другого класса. Покажем, как клиент main() отслеживает две исключительные ситуации.

try {

 

 

 

// вычисление ответа

fraction(numer,denom,ans);

}

cout « MSG: :msg(5) «

ans «"\n\n";

// действительный ответ

catch (const ZeroDenom& zd)

 

// нулевой знаменатель

{ zd.printO; }

 

&nd)

 

// отрицательное значение

catch (const NegativeDenom

«

{ cout « nd.getMsgO

«

nd.getValO

"\n\n"; }

Соседние файлы в предмете Программирование на C++