Теперь разберемся с тем, как и в каком порядке вызываются деструкторы при генерации исключения. Для этого, во-первых, добавим в деструктор класса ТАггау вывод сообщения, чтобы видеть вызов деструктора:
ТАггау::-TArray()
{ cout << "Destuctor" << endl; delete[]data; data = 6;
}:
Аналогично модифицируем конструктор копирования, чтобы видеть на экране входы в конструктор.
Напишем несколько функций, последовательно вызывающих друг друга. В каждой функции создадим «умный» массив с помощью конструктора копирования (чтобы конструктор не вызывался при передаче параметров, будем передавать их по константной ссылке). В самой «внутренней» функции сгенерируем исключение. И наконец, в главной программе будем перехватывать и обрабатывать возникающие исключения (листинг 7.12).
Листинг 7.12. Проверка «раскрутки» стека
ТАггау f2 (const ТАггау &t)
{ cout << "function f2" << endl;
TArray r(t);
cout << r << endl;
throw TArray::bad_Index();
return r;
}
TArray fl (const TArray &t)
{ cout << "function fl" << endl;
TArray R[] = { t, t, t };
cout << R[0] << endl;
cout << R[l] << endl;
cout << R[2] << endl;
TArray r = f2(t);
return г;.
}
TArray f(const TArray& a)
// самая "внутренняя" функция
// отслеживаем вход
// конструируем массив
// выполняется
// генерация исключения
// не выполняется!!!!!
// отслеживаем вход
// создается три массива
// выполняется
// выполняется
// выполняется
// г не создается - не "успевает"
// не выполняется!!!!!
продолжение &
Листинг 7.12 (продолжение)
{ cout << "function f" << endl; // отслеживаем вход
ТАггау t = fl(a); // t не создается - не "успевает"
cout << t << endl; // не выполняется!!!!
return t; //не выполняется!!!!
}
int main()
{ double a[5] = {1.2,3,4,5};
TArray B(a, a+(sizeof(a)/sizeof(double)));
try { f(B); // контролируем
}
// секции-ловушки
catch(TArray::bad_Index) {cout <<"Индекс плохой!"<< endl;} catch(TArray::bad_Range) {cout <<"Диапазон плохой!"<< endl;} catch(TArray::bad_Size) {cout <<"Размер плохой!"<< endl;}
// непредвиденное исключение
catch(...){cout <<"Exceptions!"<< endl;} return 0;
}
При запуске программы мы увидим на экране следующее:
function f function fl
Конструктор копирования! Конструктор копирования! Конструктор копирования! 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 function f2
Конструктор копирования!
1 2 3 4 5
Destuctor
Destuctor
Destuctor
Destuctor
Индекс плохой!
Последовательность действий в этом примере следующая:
1. В главной функции объявляется массив а и из него конструируется «умный» массив В.
2. Выполняется вход в контролируемый блок и вызывается функция f (), которой передается параметр — «умный» массив В.
3. В функции первым выполняется оператор вывода, и на экране появляется первая строка:
function f
4. Во второй строке функции f () прописано объявление «умного» массива t, однако до вызова конструктора дело не доходит, так как вызывается функция f 1 ().
5. Первый оператор функции f 1() выводит на экран строку:
function fl
6. В функции f 1 () создается массив R из трех «умных» массивов, и конструктор копирования вызывается трижды.
7. Выполняется вывод на экран трех элементов-массивов.
8. Хотя объявление «умного» массива г прописано, вызова конструктора не происходит, так как вызывается функция f 2 ().
9. Функция f 2 () выводит на экран строку:
function f2
Создается локальный «умный» массив г, конструктор копирования отрабатывает один раз.
10. Содержимое массива г выводится на экран.
11. Генерируется исключение bad_Index.
К этому моменту у нас создаются один локальный массив в функции f 2 () и массив R из трех массивов в функции f 1 (), которые находятся в стеке. При генерации исключения деструктор вызывается четыре раза, что мы и наблюдаем на экране. После этого происходит переход в секцию-ловушку и выводится строка «Плохой индекс». Затем выполняется выход из секции в тело главной функции и программа завершает работу.
Теперь распределим обработку исключения по разным функциям. Добавим в функцию f 1 () контролирующий блок и идентифицируем вывод в секции-ловушке в главной программе (листинг 7.13).
Листинг 7.13. Распределенная обработка исключения
TArray fl (const TArray &t)
{ cout << "function fl" << endl;
TArray R[] = { t, t, t };
cout << R[0] << endl;
cout << R[l] << endl;
cout << R[2] << endl;
try { // контролируем вызов f2
TArray r = f2(t); // локальный объект
return r; // нормальное завершение
}
catch(TArray::bad_Index)
{ cout <<"fl. Плохой индекс!"<< endl;
throw; // повторная генерация исключения
};
}
int main()
{ double a[5] = {1,2,3,4,5};
TArray B(a, a+(sizeof(a)/sizeof(double)));
try { f(B); // контролируем
}
// секции-ловушки
catch(TArray::bad_Index) {cout <<"Main. Индекс плохой!"<< endl;} catch(TArray::bad_Range) {cout <<"Диапазон плохой!"<< endl;} catch(TArray::bad_Size) {cout <<"Размер плохой!"<< endl;}
Листинг 7.13 (продолжение)
11 непредвиденное исключение
catch(...){cout <<"Exceptions!"<< endl;} return 0;
}
Остальные функции оставим без изменения. При запуске этой программы на экране появится следующее:
function f function fl
Конструктор копирования! Конструктор копирования! Конструктор копирования! 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 function f2
Конструктор копирования!
12 3 4 5 //до этого момента поведение повторяется
Destuctor // при выходе из f2
fl. Плохой индекс1 // секция-ловушка в функции fl
Destuctor // выход из fl
Destuctor
Destuctor
Main. Плохой индекс! // секция-ловушка в main()
Поведение программы повторяется до генерации исключения. Выполнение оператора throw приводит к выходу из функции f2(), при этом вызывается деструктор. В вызывающей функции обнаруживается секция-ловушка нужного типа, которая и выполняется. В ее конце срабатывает оператор повторной генерации исключения. Происходит выход из функции f 1 () — при этом трижды вызываются деструктор для уничтожения элементов массива R. В вызывающей функции — а это уже главная функция main О — ищется секция-ловушка нужного типа. Она у нас в программе есть, и мы наблюдаем ее работу в последней строке, выведенной на экран.
В связи с тем, что деструктор вызывается для уничтожения локальных объектов при генерации любого исключения, сам деструктор генерировать исключения не должен (но, вообще говоря, может). Как указывает Герб Саттер в [21], «все деструкторы должны разрабатываться так, как если бы они имели спецификацию исключения throw(), то есть ни одному исключению не должно быть позволено выйти за пределы деструктора». Если же это произойдет, то программа завершается аварийно. Это означает, что исключения, возникающие в деструкторе, должны быть перехвачены и обработаны там же.
У программистов часто заканчиваются чернила в катриджах, которые найти можно тут - http://bigcmyk.ru/shop/product/006R01517-toner-kartridzh-chernyj-26k-xerox-wc-75xx-78xx.
Деструкторы наших классов ТАггау и TDeque исключений не генерируют — они просто возвращают память. Как мы знаем, по стандарту (см. п. 18.4 в [1]) функции возврата памяти имеют прототипы:
void operator delete (void *) throw(); void operator delete [](void *) throw();
Пустая спецификация исключений показывает, что стандарт гарантирует отсутствие исключений при возврате памяти. |