C++ Программирование в среде С++ Builder 5

Исключения, конструкторы и деструкторы


Следующие два параграфа посвящены процессам, происходящим при генерировании исключения в конструкторе класса.

Локальные (автоматические) объекты

Когда выброшено исключение, начинается разматывание стека с вызовом необходимых деструкторов. Однако деструкторы в этом случае вызываются только для полностью конструированных локальных объектов. Это означает, что если исключение выброшено в конструкторе объекта, для самого этого объекта деструктор вызван не будет. Будут вызваны только деструкторы его элементов-объектов и базовых классов. Поэтому, если объект содержал уже к этому времени указатели, например, на выделенную динамическую память, она освобождаться не будет. Возникнет утечка памяти.

Рассмотрите такой пример:

Листинг 12.4. Исключение в конструкторе

/////////////////////////////////////////////////////

// Construct.срр: Исключение в конструкторе. //

#inciude <stdio.h>

#include <stdlib.h>



#include <string.h>

#pragma hdrstop

#include <condefs.h>

void* operator new[](size_t size)

// Глобальная new[].

{

printf("Global new[].\n");

return malloc(size);

}

void operator delete[](void *p) // Глобальная delete[].

{

printf("Global delete[].\n");

free (p) ;

}

class Hold { // Класс, содержащий динамический массив char. char *ptr;

public:

Hold(char *str) // Конструктор преобразования из char*.

{

printf("Constructor.\n") ;

ptr = new char[strlen(str)+1] ;

strcpy(ptr, str) ;

// printf("Constructor: throwing exception...\n");

// throw "Exception!";

} ~Hold() // Деструктор.

{

printf("Destructor.\n") ;

delete [ ] ptr;

}

void Show() // Распечатка строки.

{

printf("My contents: %s\n", ptr);

} };

int main() {

try {

Hold h = "Some string."; // Попытка конструировать

// объект. h.Show() ;

} catch(char *str) {

printf("Message caught: %s\n", str);

}

printf("Exiting main...\n");

return 0;

}

Программа создает локальный в try-блоке объект класса Hold. Строка в конструкторе, выбрасывающая исключение, пока закомментирована, и программа выводит:


Constructor.

Global new[].

My contents: Some string.

Destructor.

Global delete [].

Exiting main...



Вопрос на сообразительность: почему мы для вывода сообщений пользовались в этом примере функцией библиотеки С printf (), а не потоковыми операциями C++?

Если же раскомментировать строку, будет выброшено исключение, причем, поскольку деструктор не полностью конструированного объекта не вызывается, операция delete [ ] для уже выделенной строки выполнена не будет:

Constructor.

Global new[].

Constructor: throwing exception...

Message caught:Exception!

Exiting main...

Отсюда можно сделать полезный методический вывод: ресурсы, подобные выделяемой памяти (например, графические объекты Windows), следует оформлять как классы, которые будут входить в целевой класс в качестве его элементов. Класс из предыдущего примера можно модифицировать примерно так:

class Hold { // Класс, содержащий динамический

// массив char. struct IChar { // Вложенный класс, инкапсулирующий

// массив. char *ptr;

IChar(char *str) {

printf("IChar: constructor.\n");

ptr = new char[strlen(str)+1];

strcpy(ptr, str) ;

}

~IChar() {

printf("IChar: destructor.\n") ;

delete [] ptr;

}

} iStr; // Элемент - объект IChar. public:

Hold(char *str) // Конструктор преобразования из char*.

iStr(str) // Инициализатор элемента iStr. {

printf("Constructor: throwing exception ...\n");

throw "Exception!";

} ~Hold() // Деструктор - ничего не делает.

{

printf("Destructor.\n");

} void Show() // Распечатка строки.

{

printf("My contents: %s\n", iStr.ptr);

} };

Как видите, действия по выделению и освобождению памяти возложены теперь на класс IChar. Он, конечно, не обязан быть вложенным, как я сделал здесь (зачем, и сам не знаю). Программа выводит:

IChar: constructor.

Global new[].

Constructor: throwing exception...

IChar: destructor.

Global delete [].

Message caught: Exception!

Exiting main...

Хотя, как видите, деструктор Hold и не вызывается, деструктор его полностью конструированного элемента iStr вызывается правильно и память освобождается.





Динамические объекты



Если объект создается с помощью операции new своего класса, и в конструкторе класса генерируется исключение, то деструктор класса не вызывается. В этом отношении все происходит совершенно так же, как описано выше для явных вызовов конструктора (т. е. для локальных объектов).

Хотя деструктор не вызывается, память объекта (не та, на которую он может ссылаться посредством указателей, а его собственная) автоматически удаляется. По сути, то же происходит и с локальными объектами, только там не полностью конструированный объект просто удаляется со стека, здесь же вызывается операция класса delete. Вот пример:



Листинг 12.5. Исключение в конструкторе динамического объекта





////////////////////////////////////////////////

// Dynamic.срр: Исключение при операции класса new.

//

#include <iostream.h>

#include <string.h>

#pragma hdrstop

#include <condefs.h>

const int MaxLen = 80;

class AClass {

char msg[MaxLen];

public:

AClass () // Конструктор, выбрасывающий исключение.

{ {

cout << "AClass: constructor." << endl;

cout << "Throwing exception..." << endl;

throw "Exception!";

}

~AClass() // Деструктор.

{

cout << "AClass: destructor."<< endl; }

void *operator new (size t size) // new класса.

{

cout<< "AClass: new." << endl;

return ::new char[size];

}

void operator delete(void *p) // delete класса.

{

cout << "AClass: delete." << endl;

::delete[] p;

}

};

int main() {

AClass *ap;

try {

ар = new AClassO; // Попытка выделить, объект.

}

catch(char *str) {

cout << "Caught a sring: " << str << endl;

)

return 0;

}

Эта программа выводит:

AClass: new.

AClass: constructor.

Throwing exception...

AClass: delete.

Caught a string: Exception!

Таким образом, при исключении память объекта освобождается операцией класса delete.


Содержание раздела