На этом шаге мы рассмотрим особенности использования конструкторов и деструкторов в исключениях.
Когда выполнение программы прерывается возникшим исключением, происходит вызов деструкторов для всех автоматических объектов, появившихся с начала входа в контролируемый блок. Если исключение было порождено во время исполнения конструктора некоторого объекта, деструкторы вызываются лишь для успешно построенных объектов. Например, если исключение возникло при построении массива объектов, деструкторы будут вызваны только для полностью построенных объектов.
Приведем пример законченной программы, иллюстрирующий поведение деструкторов при обработке исключений.
//EXC14_1.СРР - вызовы деструкторов при исключении. #include <iostream.h> // Описание потоков ввода-вывода. #include <new.h> // Описание функции set_new_handler. #include <cstring.h> // Описание класса cstring. // Определение класса "блок памяти". class Memory { char *ptr; public: Memory() // Конструктор выделяет 60 Кбайт памяти. { ptr = new char [61440U] ; } ~Memory() // Деструктор очищает выделенную память. { delete ptr; } }; // Определение класса "Набор блоков памяти": class BigMemory { static int nCopy; // Счетчик экземпляров класса + 1. Memory *MemPtr; // Указатель на класс Memory. public: // Конструктор с параметром по умолчанию. BigMemory(int n = 3) { cout << endl << nCopy << ": "; MemPtr = new Memory [n]; cout << "Успех!"; // Если память выделена успешно, ++nCopy; // увеличиваем счетчик числа экземпляров. } ~BigMemory() // Деструктор очищает выделенную память. { cout << endl << --nCopy << ": Вызов деструктора"; delete [] MemPtr; } }; // Инициализация статического элемента: int BigMemory::nCopy = 1; // Указатель на старый обработчик для new: void (*old_new_handler)(); // Новый обработчик ошибок: void new_new_handler() throw (xalloc) { // Печатаем сообщение... cout << "Ошибка при выделении памяти!"; // ... и передаем управление старому обработчику (*old_new_handler)(); } void main(void) { // Устанавливаем новый обработчик: old_new_handler = set_new_handler(new_new_handler); try // Контролируемый блок { // Запрашиваем 1000 блоков по 60 Кбайт: BigMemory Request1(1000); // Запрашиваем 1000 блоков по 60 Кбайт: BigMemory Request2(1000); // Запрашиваем 50000 блоков по 60 Кбайт: BigMemory Requests(5000); } catch (xmsg& X) // Передача объекта по ссылке. { cout << "\nОбнаружено исключение " << X.why(); cout << " класса " << __throwExceptionName; } set_new_handler(old_new_handler); }
Результат выполнения программы:
Рис.1. Результат работы приложения
Заметим, что обычно вызовы деструкторов происходят по умолчанию. Однако эти умалчиваемые вызовы можно отменить с помощью опции компилятора -xd (смотри шаг 17). В этом случае результат выполнения программы будет таким:
Рис.2. Результат работы приложения
На следующем шаге мы познакомимся с динамической идентификацией типов (RTTI).