790 |
Часть IV • Расширенное использование С-^нн |
Синтаксис оператора инициализации в заголовке файла должен:
•Указывать, что статический компонент принадлежит шаблону
•Определять тип статического компонента
•Определять область видимости статического компонента
•Определять имя и исходное значение
Приведем примерный вид оператора инициализации для статического элемента данных top класса Stack. Его тип — Stack<T>*, область видимости — Stack<T>, имя — top, а исходное значение — NULL.
template <Glass Т> |
/ / он принадлежит шаблону |
Stack<T>* Stack<T>::top |
= NULL; |
Клиент может объявить только один объект указанного типа. Например, для сте ка целых значений создание экземпляра шаблона выглядит следуюш.им образом.
Stack<int> s; |
/ / только один объект для типа |
Поскольку все стеки целых величин совместно использует статический компо нент, указываюш^ий на вершину связанного списка, создание более чем одного объекта этого типа — не слишком хорошая идея.
Специализации шаблонов
В C + + понятие шаблона основывается на предположении, что алгоритм рабо тает одинаково для различных типов данных. Поэтому имеет смысл написать толь ко один класс. Иногда это предположение не выполняется. Алгоритм работает одинаково для различных типов данных, но для одинаковых типов некоторые детали алгоритма должны быть реализованы по-разному.
Рассмотрим шаблонный класс Array, который содержит набор данных (ком понентный тип) и позволяет юшентской программе проверять, можно ли найти в наборе указанный элемент (компонентный тип).
template <class Т> class Array {
Т *clata;
int size;
Array (const Array&);
operator = (const |
Array&); |
public: |
|
|
Array(T |
items[], |
int n) : size(n) |
{ data |
= new T[n]; |
/ / |
массив данных в динамически |
/ / |
выделяемой области памяти |
/ / |
размер массива |
/ / |
конструктор преобразования |
/ / |
выделить память в динамически |
/ / |
распределяемой области |
|
i f |
(clata==0) |
|
|
|
|
|
|
|
|
{ cout « |
"Out of |
memory\n"; |
e x i t ( l ) ; } |
|
for (int |
i=0; |
i |
< n; |
i++) |
/ / |
скопировать входные данные |
|
|
clata[i] = items[i]; |
} |
|
|
int find (const |
T& val) |
const |
|
|
{ |
for |
(int |
i = 0; |
i < size; |
i++) |
|
|
|
i f |
(val |
== clata[i]) |
return i ; |
/ / |
вернуть индекс |
|
return - 1 ; } |
|
|
|
|
/ / |
иначе вернуть -1 |
"Array О |
|
|
|
|
|
|
|
{ |
delete [ ] data; |
} |
|
|
|
|
Глава 17 # Шаблоны как еще одно средство проектирования |
| 791 |
Этот шаблонный класс содержит только конструктор, метод f ind() и деструктор. Конструктор выделяет динамически распределяемую область памяти, достаточ ную для ввода данных, и копирует массив ввода в память динамически рас пределяемой области. Метод find() осуществляет поиск по памяти массива. Если значение параметра не найдено, возвращается - 1 . Если найдено, метод возвра щает индекс для значения. Деструктор освобождает динамически распределяе мую область памяти.
Клиентская программа создает экземпляр объекта Array типа int, инициализи рует его и выводит на печать результаты поиска для указанного значения.
int mainO |
|
|
|
|
|
{ int |
datal |
[ ] - { 1 , 2, 3, |
4, 5); |
|
int |
nl = sizeof(data1)/sizeof(int); |
/ / число компонентов |
cout |
« " I n i t i a l |
data: |
|
"; |
|
for |
(int j |
= 0; j |
< n1; |
j++) |
// печать входных данных |
{ cout « |
datal[j]« |
" "; } |
cout « endl; |
|
|
|
// объект-массив |
Array<int> a1(data1,n1); |
int item1 = 3; int idx; |
|
if ((idx = al.find(iteml)) != -1) |
idx « endl; |
cout « |
"Item " « |
item1 « " is at index '' « |
return 0; }
Это должно совершенно одинаково работать для целых, символов, даже для объектов Point. Для каждого из данных типов объект Array будет содержать независимую копию входных значений, и операция сравнения в методе find() будет одинаково хорошо работать. Если для объекта Array создается экземпляр для компонента типа массива символов, то конструктор и метод f ind() столкнутся с проблемами.
Array<char*> a2(data2, п2);
Здесь array data2[] является массивом символьных строк. Конструктор шаб лона кг ray скопирует указатели на строки, а не сами строки. Когда данные посту пают из жестко запрограммированного набора, не возникает проблем. В реальной жизни данные поступают из внешнего источника (а не из жестко запрограммиро ванных массивов), и для каждого входного значения должна быть выделена неза висимая область памяти. Конструктор Array этого не делает. Указатели, которые он копирует в контейнер, обозначают символьный массив в памяти клиента. Подобным образом метод findO сравнивает адреса строк, а не их содержимое. Видно, что для символьного массива как компонента Array общая форма шаблон ного класса не работает — требуется скопировать строки в конструктор и срав нить их в find().
C++ поддерживает концепцию специализации при работе с параметрами типа, которые требуют специальной обработки. Для каждого специального класса должен предусматриваться отдельный специализированный шаблон класса. Син таксис описания специализации представляет собой объединение синтаксиса для самого шаблонного класса (в списке параметров шаблона) и инициализации шаблона в клиентской программе (в списке фактических типов). Параметр типа берется из списка параметров шаблона и перемещается в список фактических типов. Если в скобках списка параметров шаблона ничего не останется, все хоро шо. Например, заголовок шаблонного класса Array:
template <class Т> |
/ / |
удалить класс Т из скобок |
class Array { |
/ / |
добавить <char*> к имени класса |
становится |
|
|
template <> |
/ / |
пустой |
список параметров шаблона |
class Array<char*> { |
/ / |
список |
фактических типов |
792Часть IV • Расширенное использование С-^-^
Вметодах специализации шаблонов описывается, что следует выполнить для конкретного типа. Обратите внимание: должны присутствовать как определение шаблона, так и специализированное определение шаблона. Специализация шаб лона реализуется с использованием того же синтаксиса, что и для объекта шаб лонного класса. Фактический тип повторяется в наименовании типа в к/шентской программе.
Initial data: |
12345 |
one two three |
four five |
Item |
3 is at index 2 |
Item |
three is |
at index 2 |
Рис. 17.6.
Вывод программы из листинга 17.6
Array<char*> a1(data2,п2); |
/ / специализированный объект шаблона |
В листинге 17.6 показана полная программа, содержащая шаб лон Array и его специализированный шаблон д/ш компонентов типа символьного массива. Драйвер тестирования инициализирует объект шаблонного класса a1 и объект специализированного шаб лона а2 и отправляет сообш,ения каждому объекту. Вывод програм мы показан на рис. 17.6.
Листинг 17.6. Пример специализации шаблонного класса |
|
|
#inclucle |
<iostream> |
|
|
|
|
|
|
|
using |
namespace std; |
|
|
|
|
|
|
template |
<class T> |
|
|
|
|
|
|
|
|
class |
Array { |
|
|
|
|
/ / |
массив данных в динамически распределяемой области памяти |
T *clata; |
|
|
|
|
|
int |
size; |
|
|
|
|
/ / |
размер массива |
|
|
Array(const Array&); |
|
|
|
|
|
|
operator |
= (const |
Array&) |
|
|
|
|
|
public: |
|
|
|
|
|
n) : size(n) |
|
/ / конструктор преобразования |
|
Array(T |
items[], |
|
int |
|
|
{ data |
= new T[n]; |
|
/ / |
выделить память в динамически |
распределяемой |
области |
i f |
(data==0) |
|
|
|
memory\n" exit(l); } |
|
|
|
{ |
cout « |
"Out of |
|
|
for (int i=0; i < n; i++) |
|
|
|
|
|
|
data[i] |
= items[i]; |
} |
|
|
|
|
int |
find |
(const |
T& val) const |
|
|
|
|
{ for |
(int i = 0; |
i < size; |
i++) |
|
|
|
|
|
i f |
(val |
== |
data[i]) |
|
|
|
|
|
|
|
|
return |
i ; |
|
|
|
|
|
|
return - 1 ; } |
|
|
|
|
|
|
|
|
|
~Array() |
|
|
|
|
|
|
|
|
|
|
{ delete |
[ ] data; |
} |
|
|
|
|
|
|
} ; |
|
|
|
|
|
|
|
|
|
|
|
|
template |
о |
|
|
|
|
// пустой список шаблона |
|
|
class Array <char * > { |
|
// тип |
специализации |
|
|
char* *data; |
|
|
|
|
// массив данных вдинамически распределяемой области |
int size; |
|
|
|
|
// размер массива |
|
|
Array(const Array&); |
|
|
|
|
|
|
operator = (const Array&); |
|
|
|
|
|
public: |
|
|
|
|
|
|
n) size(n) |
// преобразование |
|
|
Array (char* items[], int |
распределяемой |
области |
{ data = new char*[n]; |
// выделить память вдинамически |
if (data==0) |
"Out of memory\n"; exit(l); } |
|
|
|
{ cout « |
|
|
for (int i=0; i < n; |
i++) |
|
// специально только для строк |
|
|
{ |
intlen = strlen (items [i]); |
|
|
|
data[i] = new char [len+1]; |
|
} } |
|
|
|
|
strcpy |
(data |
[i], items [i]); |
|
|
Глава 17 • Шаблоны как еще одно средство проектирования |
793 |
int find (const char*& val) const |
|
|
|
|
{ for(int i = 0; i < size; i++) |
// специально только для строк |
|
if ( strcmp (val, data [i])== 0) |
|
return i ; |
|
|
|
|
|
return -1; } |
|
|
|
|
|
|
"ArrayO |
|
|
|
|
|
|
{ delete [] data; } |
|
|
|
|
|
|
} |
|
|
|
|
|
|
int mainO |
|
|
|
|
|
|
{ |
|
; |
|
|
|
|
int data1[] ={1, 2, 3. 4, 5} |
'four", "five" }; |
|
char* data2[] = { "one", "two", "three |
|
int n1 = sizeof(datal)/sizeof(int); |
// число компонентов |
|
int n2 = sizeof(data2)/sizeof(char*); |
|
|
|
|
cout « "Initial data: "; |
|
// вывод входных данных |
|
for (int j = 0; j < nl; j++) |
|
|
{ cout « datal[j] « |
" "; } |
|
|
|
|
|
cout « endl ; |
|
|
|
|
|
|
for (int i = 0; i < n2; i++) |
|
// вывод входных данных |
|
{ cout « data2[i] « |
" "; } |
|
|
cout « endl; |
n1); |
|
// объект-массив |
|
Array<int> a1(data1, |
|
|
Array<char*> a2(data2, n2); |
|
// специализированный объект |
|
int item1 = 3; int idx; |
|
|
|
|
|
char* item2 = "three"; |
|
|
|
|
|
if ((idx = al.find(iteml)) ! =-1) |
|
idx « |
endl; |
|
cout « "Item " « |
item1 « |
is at index '' « |
|
if ((idx = a2.find(item2)) != |
-1) |
|
idx « |
endl; |
|
cout « "Item " « |
item2 « |
is at index '' « |
|
return 0; |
|
|
|
|
|
|
}
Язык C + + также поддерживает частичные специализации. Они обеспечивают специальную обработку только одного из нескольких параметров типа. Например,
шаблонный классDietEntry излистинга 17.4поддерживаетдва типа параметров:
template <class Key,class Data> |
|
class DictEntry { |
|
Key key; |
|
Data info; |
|
public: |
// остальная часть класса |
. . . } ; |
Для созданного экземпляра символьного массива типа Key специализирован ный шаблон создается перемеш,ением параметра Key из списка параметров шаб
лона и добавлением специализированного типа (в угловых скобках) к имени
класса.
template <class Data> |
// удалить тип Key |
class DictEntry <char*>{ |
// добавить специализированный тип |
char* key; |
// заменить тип Key |
Data info; |
|
public: |
// остальная часть класса |
. . . . } ; |
794 |
Чость IV # Расширенное использование С4«Ф |
На этом история не заканчивается. Вы можете выполнить специализацию любого количества параметров типа. Когда все параметры специализированы, остаются пустые угловые скобки в списке параметров шаблона. Пример для класса Diet Entry:
template < > |
/ / |
удалить типы обоих параметров |
class DictEntry <char*, char*>{ |
/ / |
добавить |
специализированные типы |
char* |
key; |
/ / |
заменить |
тип |
Key |
char* |
info; |
/ / |
заменить |
тип |
Data |
public: |
|
|
|
|
|
. . . . } |
; |
/ / |
остальная часть класса |
Если специальным образом должен интерпретироваться только второй пара метр, это не проблема. Однако следует повторить первый параметр типа в списке фактических типов.
template <class |
Кеу> |
class DictEntry <Кеу, char*> { |
Key key; |
|
char* |
info; |
|
public: |
|
|
. . . } |
; |
/ / остальная часть класса |
Когда компилятор обрабатывает созданный экземпляр шаблона, он выбирает наиболее специализированное определение, которое отвечает всем требованиям. Если подобного определения нет, компилятор использует общий шаблонный класс для создания объекта.
Использование специализации часто необходимо, когда некоторый компонент ный тип требует специальной обработки. Применение специализаций усложняет программу. Не все компиляторы хорошо поддерживают специализации. Когда один из типов данных требует специальной обработки (наиболее часто это симво льный массив), рассмотрите вариант написания отдельного класса с отдельным именем, например CharArray. В этом случае вы не будете сомневаться, что класс используется для создания экземпляра объекта. Однако вы не имеете гарантий в том, что подобная возможность интерпретируется одинаковым образом в раз личных специальных классах.
Иногда такого компромисса трудно достичь. Специализированные классы в C + + предлагают один из способов решения этой проблемы.
Шаблонные функции
Автономную функцию, не являюноуюся членом класса, можно определить как шаблон. Синтаксис определения подобен синтаксису функций-членов шаблонных классов.
template <class Т> void swap(T& х, Т& у)
{ Т а = х; X = у; у = а; }
Когда функции требуется прототип, она также содержит список параметров шаблона, причем после каждого зарезервированного слова класса указывается параметр.
template <class Т> void swap(T& х, Т& у);
Определение и прототип (предварительное объявление) начинаются с зарезер вированного слова template, за которым в угловых скобках указывается список
Глава 17 • Шаблоны как еще одно средство проектирования |
[ 795 | |
формальных параметров. Каждый формальный параметр состоит из зарезервиро ванного слова class и идентификатора, определенного программистом. Зарезер вированное слово class и идентификатор разделяются запятыми. Идентификатор в списке параметров должен указываться только один раз.
template <class Т, class Т> |
// это не допускается |
void swap(T& х, Т& у) |
|
{ Т а = х; X = у; у = а; } |
|
Каждый параметр типа должен использоваться в списке параметров шаблон ной функции. Если параметр типа отсутствует в списке параметров, компилятор помечает его как синтаксическую ошибку.
template <class Т> |
|
int isEmpty(void); |
/ / ошибка компиляции для глобальной функции |
Нешаблонные и шаблонные функции могут быть объявлены как extern, inline или static. Спецификатор располагается после списка формальных параметров шаблона и предшествует типу результата функции.
template <class Т>
inline void swap(T& x, T& у) |
/ / функция inline |
{ Т a = x; X = у; у = a; } |
|
Обрабатывая определение шаблонной функции, компилятор не генерирует объектную программу. Создание экземпляра шаблонной функции осуш^ествляется при ее вызове. Поскольку каждый фактический -параметр указывается в списке параметров именем, а его тип известен компилятору, при вызове шаблона не требуется указывать тип фактического параметра.
int а=5. Ь=10; double с=3.О, d=4.0; |
|
|
|
|
|
swap(a,b); |
/ / |
создание |
экземпляра |
для |
integers |
swap(c,d); |
/ / |
создание |
экземпляра |
для |
double |
Компилятор генерирует код swap(int&, int&) и swap(double&, doubles), по скольку знает типы фактических параметров а и b для первого вызова и с и d для второго вызова.
Возвраш,аемое значение не оценивается для согласования параметров. Вы мо жете выполнить преобразование. Однако д/1я параметров шаблона неявные пре образования не используются. Если компилятор не может решить, какую функцию сгенерировать для точного соответствия параметров, это синтаксическая ошибка.
swap(a,c); |
/ / синтаксическая ошибка: нет точного соответствия |
Допускается перегрузка шаблонных функций. Предусматривается, что они могут различаться по типам фактических параметров или по количеству параметров.
template <class Т>
inline void swap(T& x, T& у, Т& z) / / три параметра
{ Т а = х; X = у; у = z; z = а; }
Эта функция может отличаться от функции swapO с тремя параметрами.
int а=5, Ь=10, с=20; swap(a,b); swap(a,b,c);
Шаблонные функции могут специализироваться для конкретных типов. Напри мер, символьные массивы не могут переставляться как целые, должна использо ваться специализированная версия. Правила формирования специализаций функции те же, что и для специализаций шаблонных классов. Список параметров шаблона
796 Часть IV # Расширенное использование C-^-t-
^^^s^^i^i;^
исчерпывается, и фактические типы (в угловых скобках) распределяются между списками функции и параметров. Специализированная функция swap():
|
|
|
|
|
|
template < > |
|
|
|
|
inline |
void swap <char*> |
(char* |
x, |
char* y) |
{ char* |
a = new char[strlen(x)+1] |
; |
char* |
b = new char[strlen(y)+1] |
; |
i f (b==NULL) |
{ cout « |
"Out of |
memory\n |
strcpy(a,x); |
strcpy(b,y); |
|
|
strcpy(x,b); |
strcpy(y,a) |
|
|
delete a; delete b; }
exit(l); }
/ / |
память должна обеспечить |
/ / |
вызывающая программа |
Клиентская программа:
char x[20]="Hello! |
y[20]=*'Hi, |
there!"; int a=5, b=10; |
swap(a,b); |
/ / |
создается экземпляр общей шаблонной функции |
swap(x,y); |
/ / |
создается экземпляр специализированной |
|
/ / |
шаблонной функции |
Компилятор вначале осуществляет поиск нешаблонной функции. Если она об наруживается и параметры точно совпадают, то шаблоны не принимаются во внимание. Если устанавливается соответствие более чем одного варианта нешаб лонной функции, это синтаксическая ошибка.
Если не обнаруживается совпадаюш.ая нешаблонная функция, то проверяются шаблоны. Если при этом устанавливается полное соответствие и уже суш,ествует
еереализация, она используется. Новая объектная программа не генерируется.
Впротивном случае создается экземпляр функции.
Если не обнаруживается совпадаюш,ая шаблонная функция, то нешаблонные функции проверяются с помош,ью неявных преобразований.
Шаблонные функции не могут вызываться или передаваться нешаблонным функциям как параметры.
Итоги
в этой главе рассматривались шаблоны C++. Если алгоритмы должны быть одинаковыми для различных типов, следует написать их всего один раз, а позже указывать, для какого фактического типа предполагается использовать алгоритм.
Однако, используя это представление на практике, вы можете столкнуться с многочисленными трудностями. Синтаксис шаблонов C++ сложен. Он услож няется также за счет использования специализаций. Иногда попытка установить, какая специализация будет вызываться, представляет собой неприятную задачу. Временами то, что работает на одном компьютере с одним компилятором, может не функционировать на другом компьютере с другим компилятором.
К тому же из-за использования шаблонов появляются дополнительные затраты памяти и снижается производительность. Именно поэтому многие программисты на C++ стараются не применять шаблоны. С другой стороны, шаблоны использу ются в Стандартной библиотеке шаблонов (Standard Template Library — STL). Вам следует понимать основные принципы их использования, чтобы правильно работать с библиотекой STL.
Это мои;ное средство. Следует использовать его с осторожностью.
м
п,^программирование^ ^ / ^ с обработкой
исключительных ситуаций
Темы данной главы
^Простые примеры обработки исключительных ситуаций
^Синтаксис исключительных ситуаций C++
^Исключительные ситуации в объектах класса
^Операции приведения типов
^Итоги
|
|
|
|
l ^ ^ X |
этой главе рассматривается |
относительно новый вопрос для языка |
Ж |
ell|fc C+ + : программирование с |
обработкой исключительных ситуаций. |
^ |
4Jl!!^^ Исключительные ситуации являются механизмом языка, позволяющим |
программисту выделить исходную программу, которая описывает исключительные ситуации, из исходной программы, описывающей основные случаи обработки. Исключительными являются ситуации, которые не должны возникать во время обычной обработки, но временами проявляются. Отделение обработки таких исключительных ситуаций от основной программы облегчает ее чтение и сопро вождение.
Это определение не совсем четкое. Действительно, то, что одни программисты рассматривают как исключительную или необычную ситуацию, другие восприни мают как неотъемлемую часть операций системы. Например, когда выделяется память в динамически распределяемой области, алгоритм должен описывать, что произойдет, если запрос удовлетворяется. Возможно, программа выйдет за преде лы памяти, тогда алгоритм также должен определить, что произойдет, если память недоступна. Является ли выход за пределы памяти исключительной ситуацией? Большинство программистов ответят утвердительно.
Когда программа интерактивно считывает данные от пользователя, работаю щего в диалоговом режиме, алгоритм определяет обработку допустимых данных. Что произойдет, если пользователь сделает ошибку и введет неверные данные? Будет создана исключительная ситуация? Большинство программистов ответят отрицательно. Ошибки в диалоговом режиме естественны, и алгоритмы обработки этих ошибок должны рассматриваться как часть функциональных возможностей основной системы, а не как что-то происходящее очень редко.
I 798 I Часть IV ^ Расширенное использование C-^-^
При считывании в цикле данных из файла алгоритм определяет, что происходит только при считывании следующей записи — как должны обрабатываться разные части записи. Возможно, в файле больше нет записей для считывания. Тогда в алгоритме должно определяться, что следует делать в этом случае. Является ли достижение конца файла исключительной ситуацией? Большинство программи стов ответят отрицательно. Это завершение одного этапа обработки (считывание записей файла) и начало следующеего (обработка данных в памяти).
Вопрос замусоривания исходной программы разнотипными вычислительными задачами достаточно важен, независимо от того, воспринимает ли программист данную ситуацию как относящуюся к основной обработке с некоторыми дополни тельными исключительными случаями (первый пример), или как набор различных случаев приблизительно равной значимости (второй и третий примеры).
Чтобы вы могли принимать интеллектуальные решения по структурированию своих алгоритмов, следует знать разнообразные средства языка программирова ния. Посмотрим, что представляют собой исключительные ситуации (как метод программирования в C+ + ), какой синтаксис они требуют от программиста, как их правильно применять и каких некорректных вариантов использования следует избегать.
Первоначально C + + не поддерживал обработку исключительных ситуаций и полагался на механизмы языка С, используя переменные, доступные всей про грамме в целом (например, еггпо), или переходы и вызовы специальных функций, имена которых зафиксированы, но содержание может определяться программи стом (например, setjmp и longjmp).
Возможности C + + по обработке исключительных ситуаций являются относи тельно новыми в языке. Механизм исключительных ситуаций сложен. Мы имеем необходимы опыт использования исключительных ситуаций. Кроме того, при ис пользовании исключительных ситуаций увеличивается время выполнения и раз мер исполняемой программы. Поэтому исключительные ситуации не рекомендуется использовать при первой предоставляющейся возможности. Однако они должны стать частью набора средств программирования.
Простой пример для обработки исключительных ситуаций
Обычно алгоритмы обработки используют операторы управления потоком, чаще всего if или switch, для отделения обычной обработки данных от обработ ки ошибочных или неверных данных. Для многошаговых алгоритмов часть ис ходной программы для основного алгоритма и для исключительных состояний записываются в разных ветвях одной и той же исходной программы. Таким обра зом затрудняется чтение программы — основная линия теряется среди множест ва исключительных и редких случаев.
Когда в функции что-то выполняется неверно, функция может не знать, что делать с ошибкой. Аварийное завершение программы может быть хорошим реше нием во многих ситуациях, например при попытке внести элемент в стек системы, который оказывается заполненным. С другой стороны, аварийное прекращение программы может не вызвать высвобождения таких ресурсов, удерживаемых программой, как открытые файлы или блокировки на уровне базы данных.
Другой подход состоит в установке или возвращении кода ошибки вызывающей программе для проверки и принятия действий по восстановлению, если они воз можны. Например, когда клиентская программа пытается извлечь элемент из пус того стека, возвращается код ошибки. Однако это не всегда можно сделать. Если какое-либо возвращаемое значение указанного типа является допустимым для функции pop, то специальное значение, возвращаемое вызывающей программе для обозначения исключительной ситуации, может отсутствовать.
Глава 18 • Программирование с обработкой исключительных ситуаций |
799 |
При таком подходе клиентская программа должна проверять возможные ошиб ки. В результате увеличивается общий размер программы, создается громоздкая клиентская программа и замедляется скорость ее выполнения. Как правило, такой подход вызывает ошибки. Некоторые функции, например конструкторы С+ + , не имеют возвраш,аемых значений. Подобный подход использоваться не может.
Определение глобальной переменной, например еггпо, для указания ошибки не работает для программ, выполняемых одновременно. Также трудно единообразно реализовать это для последовательных программ, поскольку требуется, чтобы клиентская программа тш,ательно проверяла значение глобальной переменной. Такие проверки засоряют клиентскую программу и затрудняют ее понимание.
С помош,ью таких библиотечных функций, как setjmp и longjmp, программа может передать управление действию, которое высвободит внешние источники
и выполнит восстановление после ошибки. Однако в этом случае стек вернется
висходное состояние без вызова деструкторов для объектов, созданных в стеке до вызова этих функций. Следовательно, ресурсы, удерживаемые этими объектами, могли бы быть высвобождены некорректно.
Давайте рассмотрим простой пример и проанализируем вопросы, которые дол жны разрешить методы обработки для исключительных ситуаций. В листинге 18.1 показана программа, которая интерактивно запрашивает у пользователя значения числителя и знаменателя для дроби, вычисляет и выводит на печать значение дроби. Для вычисления результата программа использует две серверные функции, inverseO и fraction(). Первая функция возврандает обратное значение для свое го аргумента. Она вызывается второй функцией fraction(), которая умножает свой первый параметр на значение, возвращенное inverse().
Это специально придуманная структура для такой вычислительной задачи. Простая структура не позволит продемонстрировать различные опции обработки исключительных ситуаций.
Л и с т и нг 18.1. Пример программы с обработкой ошибок в клиентской программе
#inclucle <iostream> using namespace std;
inline void |
inverse(long value, double& answer) |
|
|
{ |
answer = 1.0/value; |
} |
|
/ / |
answer |
= 1/value |
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 « |
"Enter numerator and positive\n" |
|
|
«"denominator (any letter toquit): ";
if ((cin » |
numer » denom) == 0) break; |
// ввести данные |
if (denom > 0) { |
// правильный ввод |
fraction(numer,denom,ans); |
// вычисление результата |
cout « |
"Value of the fraction: " « |
ans «"\n\n"; |
} |
|
// неверный результат |
else if (denom == 0) |
cout « |
"\nZero denominator is not allowed\n\n"; |
else |
"\nNegative denominator: " « |
denom «"\n\n"; } |
cout « |
return 0;
}