Выбрать главу

// t уничтожается автоматически

// отображается затраченное время ...

}

Чтобы убедиться в том, что мы понимаем поведение деструктора (да и конструктора тоже), разберем следующий пример:

(1) #include "Account.h"

(2) Account global( "James Joyce" );

(3) int main()

(4) {

(5) Account local( "Anna Livia Plurabelle", 10000 );

(6) Account &loc_ref = global;

(7) Account *pact = 0;

(8)

(9) {

(10) Account local_too( "Stephen Hero" );

(11) pact = new Account( "Stephen Dedalus" );

(12) }

(13)

(14) delete pact;

(15) }

Сколько здесь вызывается конструкторов? Четыре: один для глобального объекта global в строке (2); по одному для каждого из локальных объектов local и local_too в строках (5) и (10) соответственно, и один для объекта, распределенного в хипе, в строке (11). Ни объявление ссылки loc_ref на объект в строке (6), ни объявление указателя pact в строке (7) не приводят к вызову конструктора. Ссылка - это псевдоним для уже сконструированного объекта, в данном случае для global. Указатель также лишь адресует объект, созданный ранее (в данном случае распределенный в хипе, строка (11)), или не адресует никакого объекта (строка (7)).

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

Конструкторы и деструкторы глобальных объектов вызываются на стадиях инициализации и завершения выполнения программы. Хотя такие объекты нормально ведут себя при использовании в том файле, где они определены, но их применение в ситуации, когда производятся ссылки через границы файлов, становится в C++ серьезной проблемой.4

Деструктор не вызывается, когда из области видимости выходит ссылка или указатель на объект (сам объект при этом остается).

С++ с помощью внутренних механизмов препятствует применению оператора delete к указателю, не адресующему никакого объекта, так что соответствующие проверки кода необязательны:

// необязательно: неявно выполняется компилятором

if (pact != 0 ) delete pact;

Всякий раз, когда внутри функции этот оператор применяется к отдельному объекту, размещенному в хипе, лучше использовать объект класса auto_ptr, а не обычный указатель (см. обсуждение класса auto_ptr в разделе 8.4). Это особенно важно потому, что пропущенный вызов delete (скажем, в случае, когда возбуждается исключение) ведет не только к утечке памяти, но и к пропуску вызова деструктора. Ниже приводится пример программы, переписанной с использованием auto_ptr (она слегка модифицирована, так как объект класса auto_ptr может быть явно переустановлен для адресации другого объекта только присваиванием его другому auto_ptr):

#include memory

#include "Account.h"

Account global( "James Joyce" );

int main()

{

Account local( "Anna Livia Plurabelle", 10000 );

Account &loc_ref = global;

auto_ptr pact( new Account( "Stephen Dedalus" ));

{

Account local_too( "Stephen Hero" );

}

// объект auto_ptr уничтожается здесь

}

14.3.1. Явный вызов деструктора

Иногда вызывать деструктор для некоторого объекта приходится явно. Особенно часто такая необходимость возникает в связи с оператором new (см. раздел 8.4). Рассмотрим пример. Когда мы пишем:

char *arena = new char[ sizeof Image ];

то из хипа выделяется память, размер которой равен размеру объекта типа Image, она не инициализирована и заполнена случайными битами. Если же написать:

Image *ptr = new (arena) Image( "Quasimodo");

то никакой новой памяти не выделяется. Вместо этого переменной ptr присваивается адрес, ассоциированный с переменной arena. Теперь память, на которую указывает ptr, интерпретируется как занимаемая объектом класса Image, и конструктор применяется к уже существующей области. Таким образом, оператор размещения new() позволяет сконструировать объект в ранее выделенной области памяти.

Закончив работать с изображением Quasimodo, мы можем произвести какие-то операции с изображением Esmerelda, размещенным по тому же адресу arena в памяти:

Image *ptr = new (arena) Image( "Esmerelda" );

Однако изображение Quasimodo при этом будет затерто, а мы его модифицировали и хотели бы записать на диск. Обычно сохранение выполняется в деструкторе класса Image, но если мы применим оператор delete: