Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

670

class Account { public:

Account();

explicit Account( const char*, double=0.0 ); // ...

};

(a)Account acct;

(b)Account acct2 = acct;

(c)Account acct3 = "Rena Stern";

(d)Account acct4( "Anna Engel", 400.00 );

объясните, что происходит в результате следующих определений:

(e) Account acct5 = Account( acct3 );

Упражнение 14.5

Параметр копирующего конструктора может и не быть константным, но обязан быть ссылкой. Почему ошибочна такая инструкция:

Account::Account( const Account rhs );

14.3. Деструктор класса

Одна из целей, стоящих перед конструктором, – обеспечить автоматическое выделение ресурса. Мы уже видели в примере с классом Account конструктор, где с помощью

оператора new выделяется память для массива символов и присваивается уникальный номер счету. Можно также представить ситуацию, когда нужно получить монопольный доступ к разделяемой памяти или к критической секции потока. Для этого необходима симметричная операция, обеспечивающая автоматическое освобождение памяти или возврат ресурса по завершении времени жизни объекта, – деструктор. Деструктор это специальная определяемая пользователем функция-член, которая автоматически вызывается, когда объект выходит из области видимости или когда к указателю на объект применяется операция delete. Имя этой функции образовано из имени класса с предшествующим символом тильда” (~). Деструктор не возвращает значения и не принимает никаких параметров, а следовательно, не может быть перегружен. Хотя разрешается определять несколько таких функций-членов, лишь одна из них будет применяться ко всем объектам класса. Вот, например, деструктор для нашего класса

Account:

С++ для начинающих

671

class Account { public:

Account();

explicit Account( const char*, double=0.0 ); Account( const Account& );

~Account(); // ...

private:

char *_name; unsigned int _acct_nmbr; double _balance;

};

inline Account::~Account()

{

delete [] _name; return_acct_number( _acct_nnmbr );

}

inline Account::~Account()

{

//необходимо delete [] _name;

return_acct_number( _acct_nnmbr );

//необязательно

_name = 0; _balance = 0.0; _acct_nmbr = 0;

Обратите внимание, что в нашем деструкторе не сбрасываются значения членов:

}

Делать это необязательно, поскольку отведенная под члены объекта память все равно

class Point3d { public:

// ...

private:

float x, y, z;

будет освобождена. Рассмотрим следующий класс:

};

Конструктор здесь необходим для инициализации членов, представляющих координаты точки. Нужен ли деструктор? Нет. Для объекта класса Point3d не требуется освобождать ресурсы: память выделяется и освобождается компилятором автоматически в начале и в конце его жизни.

В общем случае, если члены класса имеют простые значения, скажем, координаты точки, то деструктор не нужен. Не для каждого класса необходим деструктор, даже если у него есть один или более конструкторов. Основной целью деструктора является освобождения

С++ для начинающих

672

ресурсов, выделенных либо в конструкторе, либо во время жизни объекта, например освобождение замка или памяти, выделенной оператором new.

Но функции деструктора не ограничены только освобождением ресурсов. Он может реализовывать любую операцию, которая по замыслу проектировщика класса должна быть выполнена сразу по окончании использования объекта. Так, широко

распространенным приемом для измерения производительности программы является определение класса Timer, в конструкторе которого запускается та или иная форма программного таймера. Деструктор останавливает таймер и выводит результаты замеров. Объект данного класса можно условно определять в критических участках программы,

{

//начало критического участка программы

#ifdef PROFILE Timer t;

#endif

//критический участок

//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). Однако в программе нет инструкции, с которой можно связать

С++ для начинающих

673

вызов деструктора. Компилятор просто вставляет эти вызовы за последним использованием объекта, но перед закрытием соответствующей области видимости.

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

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

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

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

необязательны:

if (pact != 0 ) delete pact;

Всякий раз, когда внутри функции этот оператор применяется к отдельному объекту, размещенному в хипе, лучше использовать объект класса auto_ptr, а не обычный указатель (см. обсуждение класса auto_ptr в разделе 8.4). Это особенно важно потому, что пропущенный вызов delete (скажем, в случае, когда возбуждается исключение) ведет не только к утечке памяти, но и к пропуску вызова деструктора. Ниже приводится пример программы, переписанной с использованием 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<Account> pact( new Account( "Stephen Dedalus" ));

{

Account local_too( "Stephen Hero" );

}

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

адресации другого объекта только присваиванием его другому auto_ptr):

}

4 См. статью Джерри Шварца в [LIPPMAN96b], где приводится дискуссия по этому поводу и описывается решение, остающееся пока наиболее распространенным.