
Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdf812 |
Часть 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. Если серверная функция генерирует исключительную ситуацию и отслеживает ее, то эта исключи тельная ситуация не должна включаться в список. В списке располагаются только те исключительные ситуации, которые должен обрабатывать клиент.
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() должна обеспечить
Глава 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"; } |