Блок отслеживания исключительных ситуаций
Этот блок представляет собой набор выражений, начинающийся ключевым словом try, 3a которым следует открывающая фигурная скобка; завершается блок закрываю- щей фигурной скобкой. Пример:
try
{
Function();
}
Блок обработки исклтчительиых ситуаций
Этот блок представпяет собой набор строк, каждая из них начинается ключевым словом catch, за которым следует тип исключения, заданный в круглых скобках. Затем идет открывающая фигурная скобка. Завершается блок-catch закрывающей фигурной скобкой.
Пример:
try
{
Function();
}
catch (OutOfMemory)
{
// выполняем дествие
}
Использование блоков try и catch
Часто не так уж просто решить, куда поместить блоки try, поскольку не всегда очевидно, какие действия могут вызвать исключительную ситуацию. Следующий вопрос состоит в том, где перехватывать исключение. Может быть, вы захотите генерировать исключения, связанные с памятью, там, где память распределяется, но в то же время перехватывать исключения стоит только в высокоуровневой части программы, связанной с интерфейсом пользователя.
При попытке определить местоположение блока try выясните, где в программе происходит распределение памяти или других ресурсов. При ошибках, связанных с выходом значений за допустимые пределы, вводом некорректных данных и пр., нужно использовать другие подходы.
Перехват исключений
Перехват исключений происходит следующим образом. Когда генерируется исключение, исследуется стек вызовов. Он представляет собой список обращений к функциям, создаваемый по мере того, как одна часть программы вызывает другую функцию.
Стек вызовов отслеживает путь выполнения программы. Если функция main() вызывает функцию Animal::GetFavoriteFood(), а функция GetFavoriteFood() — функцию Animal::LookupPreferences(), которая, в свою очередь, вызывает функцию fstream::operator>>(), то все эти вызовы заносятся в стек вызовов. Рекурсивная функция может оказаться в стеке вызовов много раз.
Исключение передается в стек вызовов для каждого вложенного блока. По мере прохождения стека вызываются деструкторы для локальных объектов, в результате чего эти объекты разрушаются.
За каждым блоком try следует один или несколько блоков catch. Если сгенерированное исключение соответствует одному из исключений операторов catch, то выполняется код блока этого оператора. Если же исключению не соответствует ни один из операторов catch, прохождение стека продолжается.
Если исключение пройдет весь путь к началу программы (функции main()) и все еще не будет перехвачено, вызывается встроенный обработчик, который завершит программу.
Прохождение исключения по стеку можно сравнить с поездкой по улице с односторонним движением. По мере прохождения стека его объекты разрушаются. Назад дороги нет. Если исключение перехвачено и обработано, программа продолжит работу после блока catch, который перехватил это исключение.
Таким образом, в листинге20.1 выполнение программы продолжится со строки 101 — первой строки после блока try catch, перехватившего исключение xBoundary. Помните, что при возникновении исключительной ситуации выполнение программы продолжается после блока catch, а не после того места, где она возникла.
Использование нескольких операторов catch
В некоторых случаях выполнение одного выражения потенциально может быть причиной возникновения нескольких исключительных ситуаций. В этом случае нужно использовать несколько операторов catch, следующих друг за другом, подобно конструкции с оператором switch. При этом эквивалентом оператора default будет выражение catch(.,,), которое следует понимать как "перехватить все". Отслеживание нескольких возможных исключений показано в листинге 20.2.
Листинг 20.2. Множественные исключения
1: #include <iostream.h>
2:
3: const int DefaultSize = 10;
4:
5: class Array
6: {
7: public:
8: // конструкторы
9: Array(int itsSize = DefaultSize);
10: Array(const Array &rhs);
11: ~Array() { delete [] pType;}
12:
13: // операторы
14: Array& operator=(const Array&);
15: int& operator[](int offSet);
16: const int& operator[](int offSet) const;
17:
18: // методы доступа
19: int GetitsSize() const { return itsSize; }
20:
21: //функция-друг
22: friend ostream& operator<< (ostream&, const Array&);
23:
24: // определение классов исключений
25: class xBoundary { } ;
26: class xTooBig { } ;
27: class xTooSmall { } ;
28: class xZero { } ;
29: class xNegative { } ;
30: private:
31: int *pType;
32: int itsSize;
33: };
34:
35: int& Array::operator[](int offSet)
36: {
37: int size = GetitsSize();
38: if (offSet >= 0J,& offSet < GetitsSize())
39: return pType[offSet];
40: throw xBoundary();
41: return pType[0]; // требование компилятора
42: }
43:
44:
45: const int& Array::operator[](int offSet) const
46: {
47: int mysize = GetitsSize();
48: if (offSet >= 0 && offSet < GetitsSize())
49: return pType[offSet]
50: throw xBoundary();
51:
52: return pType[0]; // требование компилятора
53: }
54:
55:
56: Array::Array(int size):
57: itsSize(size)
58: {
59: if (size == 0)
60: throw xZero();
61: if (size < 10)
62: throw xTooSmall();
63: if (size > 30000)
64: throw xTooBig();
65: if (size < 1)
66: throw xNegative();
67:
68: pType = new int[size];
69: for (int i = 0; i<size: i++)
70: pType[i] = 0;
71: }
72:
73:
74:
75: int main()
76: {
77:
78: try
79: {
80: Array intArray(0);
81: for (int j = 0; j< 100; j++)
82: {
83: intArray[j] = ];
84: cout << "intArray[" << j << "] okay...\n";
85: }
86: }
87: catch (Array::xBoundary)
88: {
89: cout << "Unable to process your input!\n";
90: }
91: catch (Array::xTooBig)
92: {
93: cout << "This array is too big...\n";
94: }
95: catch (Array::xTooSmall)
96: {
97: cout << "This array is too small...\n";
98: }
99: catch (Array::xZero)
100: {
101: cout << "You asked for an array";
102: cout << " of zero objects!\n";
103: }
104: catch (... )
105: {
106: cout << "Something went wrong!\n";
107: }
108: cout << "Done.\n";
109: return 0;
110: }
Результат:
You asked for an array of zero objects!
Done
Анализ: В строках 26—29 создается четыре новых класса: xTooBig, xTooSmall, xZero и xNegative. В строках 56—71 проверяется размер массива, переданный конструктору. Если он слишком велик или мал, а также отрицательный или нулевой, генерируется исключение.
За блоком try следует несколько операторов catch для каждой исключительной ситуации, кроме исключения, связанного с передачей отрицательного размера. Данное исключение перехватывается оператором catch(. ..) в строке 104.
Опробуйте эту программу с рядом значений для размера массива. Затем попытайтесь ввести значение -5. Вы могли бы ожидать, что будет вызвано исключение xNegative, но этому помешает порядок проверок, заданный в конструкторе: проверка size < 10 выполняется до проверки size < 1. Чтобы исправить этот недостаток, поменяйте строки 61 и 62 со строками 65 и 66 и перекомпилируйте программу.
Наследование исключений
Исключения — это классы, а раз так, то от них можно производить другие классы. Предположим, что нам нужно создать класс xSize и произвести от него классы xZero, xTooSmall, xTooBig и xNegative. В результате для одних функций можно установить перехват ошибки xSize, а для других — перехват типов ошибок, произведенных от xSize. Реализация этой идеи показана в листинге 20.3.
Листинг 20.3. Наследование исключений
1: #include <iostream.h>
2:
3: const int DefaultSize = 10;
4:
5: class Array
6: {
7: public:
8: // конструкторы
9: Array(int itsSize = DefaultSize);
10: Array(const Array &rhs);
11: ~Array() { delete [] pType;}
12:
13: // операторы
14: Array& operator=(const Array&);
15: int& operator[](int offSet);
16: const int& operator[](int offSet) const;
17:
18: // методы доступа
19: int GetitsSize() const { return itsSize; }
20:
21: // функция-друг
22: friend ostream& operator<< (ostream&, const Array&);
23:
24: // определения классов исключений
25: class xBoundary { };
26: class xSize { };
27: class xTooBig : public xSize { };
28: class xTooSmall : public xSize { };
29: class xZero : public xTooSmall { };
30: class xNegative : public xSize { };
31: private:
32: int *pType;
33: int itsSize;
34: };
35:
36:
37: Array::Array(int size):
38: itsSize(size)
39: {
40: if (size — 0)
41: throw xZero();
42: if (size > 30000)
43: throw xTooBig();
44: if (size <1)
45: throw xNegative();
46: if (size < 10)
47: throw xTooSmall();
48:
49: pType = new int[size];
50: for (int i = 0; i<size; i++)
51: pType[i] = 0;
52: }
53:
54: int& Array::operator[](int offSet)
55: {
56: int size = GetitsSize();
57: if (offSet >= 0 && offSet < GetitsSize())
58: return pType[offSet];
59: throw xBoundary();
60: return pType[0]; // требование компилятора
61: }
62:
63:
64: const int&Array::operator[](int offSet) const
65: {
66: int mysize = GetitsSize();
67: if (offSet >= 0 && offSet < GetitsSize())
68: return pType[offSet];
69: throw xBoundary();
70:
71: return pType[0]; // требование компилятора
72: }
73:
74: int main()
75: {
76:
77: try
78: {
79: Array intArray(0);
80: for (int j = 0; j< 100; j++)
81: {
82: intArray[j ] = j;
83: cout << "intArray[" << j << "] okay...\n";
84: }
85: }
86: catch (Array::xBoundary)
87: {
88: cout << "Unable to process your input!\n";
89: }
90: catch (Array::xTooBig)
91: {
92: cout << "This array is too big...\n";
93: }
94:
95: catch (Array::xTooSmall)
96: {
97: cout << "This array is too small...\n";
98: }
99: catch (Array::xZero)
100: {
101: cout << "You asked for an array";
102: cout << " of zero objects!\n";
103: }
104:
105:
106: catch (.. .)
107: {
108: cout << "Something went wrong!\n";
109: }
110: cout << "Done.\n";
111: return 0;
112: }
Результат:
This array is too small...
Done.
Анализ: Здесь существенно изменены строки 27—30, где устанавливается иерархия классов. Классы xTooBig, xTooSmall и xNegative произведены от класса xSize, а класс xZero — от класса xTooSmall.
Класс Array создается с нулевым размером, но что это значит? Казалось бы, неправильное исключение будет тут же перехвачено! Однако тщательно исследуйте блок catch, и вы поймете, что, прежде чем искать исключение типа xZero, в нем ищется исключение типа xTooSmall. А поскольку возник объект класса xZero, который также является объектом класса xTooSmall, то он перехватывается обработчиком исключения xTooSmall. Будучи уже обработанным, это исключение не передается другим обработчикам, так что обработчик исключений типа xZero никогда не вызывается.
Решение этой проблемы лежит в тщательном упорядочении обработчиков таким образом, чтобы самые специфические из них стояли в начале, а более общие следовали за ними. В данном примере для решения проблемы достаточно поменять местами два обработчика — xZero и xTooSmall.