Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

Таким образом, при уничтожении объекта сначала вызывается деструктор класса, а затем определенный в классе оператор delete() для освобождения памяти. Если значение ptr равно 0, то ни деструктор, ни delete() не вызываются.

15.8.1. Операторы new[ ] и delete [ ]

Оператор new(), определенный в предыдущем подразделе, вызывается только при выделении памяти для единичного объекта. Так, в данной инструкции вызывается new()

// вызывается Screen::operator new()

класса Screen:

Screen *ps = new Screen( 24, 80 );

тогда как ниже вызывается глобальный оператор new[]() для выделения из хипа памяти

// вызывается Screen::operator new[] ()

под массив объектов типа Screen:

Screen *psa = new Screen[10];

В классе можно объявить также операторы new[]() и delete[]() для работы с массивами.

Оператор-член new[]() должен возвращать значение типа void* и принимать в качестве

class Screen { public:

void *operator new[] ( size_t );

// ...

первого параметра значение типа size_t. Вот его объявление для Screen:

};

Когда с помощью new создается массив объектов типа класса, компилятор проверяет, определен ли в классе оператор new[](). Если да, то для выделения памяти под массив вызывается именно он, в противном случае – глобальный new[](). В следующей инструкции в хипе создается массив из десяти объектов Screen:

Screen *ps = new Screen[10];

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

Даже если в классе имеется оператор-член new[](), программист может вызвать для создания массива глобальный new[](), воспользовавшись оператором разрешения глобальной области видимости:

Screen *ps = ::new Screen[10];

Оператор delete(), являющийся членом класса, должен иметь тип void, а в качестве

class Screen { public:

void operator delete[]( void * );

первого параметра принимать void*. Вот как выглядит его объявление для Screen:

};

Чтобы удалить массив объектов класса, delete должен вызываться следующим образом:

delete[] ps;

Когда операндом delete является указатель на объект типа класса, компилятор проверяет, определен ли в этом классе оператор delete[](). Если да, то для освобождения памяти вызывается именно он, в противном случае – его глобальная версия. Параметр типа void* автоматически инициализируется значением адреса начала области памяти, в которой размещен массив.

Даже если в классе имеется оператор-член delete[](), программист может вызвать глобальный delete[](), воспользовавшись оператором разрешения глобальной области видимости:

::delete[] ps;

Добавление операторов new[]() или delete[]() в класс или удаление их оттуда не отражаются на пользовательском коде: вызовы как глобальных операторов, так и операторов-членов выглядят одинаково.

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

При уничтожении массива сначала вызывается деструктор класса для уничтожения элементов, а затем оператор delete[]() – для освобождения всей памяти. При этом важно использовать правильный синтаксис. Если в инструкции

delete ps;

ps указывает на массив объектов класса, то отсутствие квадратных скобок приведет к вызову деструктора лишь для первого элемента, хотя память будет освобождена полностью.

У оператора-члена delete[]() может быть не один, а два параметра, при этом второй должен иметь тип size_t:

class Screen { public:

//заменяет

//void operator delete[]( void* );

void operator delete[]( void*, size_t );

};

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

15.8.2. Оператор размещения new() и оператор delete()

Оператор-член new() может быть перегружен при условии, что все объявления имеют

class Screen { public:

void *operator new( size_t ); void *operator new( size_t, Screen

* );

// ...

разные списки параметров. Первый параметр должен иметь тип size_t:

};

Остальные параметры инициализируются аргументами размещения, заданными при

void func( Screen *start ) { Screen *ps = new (start)

Screen;

// ...

вызове new:

}

Та часть выражения, которая находится после ключевого слова new и заключена в круглые скобки, представляет аргументы размещения. В примере выше вызывается оператор new(), принимающий два параметра. Первый автоматически инициализируется значением, равным размеру класса Screen в байтах, а второй – значением аргумента размещения start.

Можно также перегружать и оператор-член delete(). Однако такой оператор никогда не вызывается из выражения delete. Перегруженный delete() неявно вызывается компилятором, если конструктор, вызванный при выполнении оператора new (это не опечатка, мы действительно имеем в виду new), возбуждает исключение. Рассмотрим использование delete() более внимательно.

Последовательность действий при вычислении выражения

Screen *ps = new ( start ) Screen;

такова:

1.Вызывается определенный в классе оператор new(size_t, Screen*).

2.Вызывается конструктор по умолчанию класса Screen для инициализации созданного объекта.

Переменная ps инициализируется адресом нового объекта Screen.

Предположим, что оператор класса new(size_t, Screen*) выделяет память с помощью глобального new(). Как разработчик может гарантировать, что память будет освобождена, если вызванный на шаге 2 конструктор возбуждает исключение? Чтобы защитить пользовательский код от утечки памяти, следует предоставить перегруженный оператор delete(), который вызывается только в подобной ситуации.

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

Screen *ps = new (start) Screen;

Если конструктор по умолчанию класса Screen возбуждает исключение, то компилятор ищет delete() в области видимости Screen. Чтобы такой оператор был найден, типы его параметров должны соответствовать типам параметров вызванного new(). Поскольку первый параметр new() всегда имеет тип size_t, а оператора delete() void*, то первые параметры при сравнении не учитываются. Компилятор ищет в классе Screen оператор delete() следующего вида:

void operator delete( void*, Screen* );

Если такой оператор будет найден, то он вызывается для освобождения памяти в случае, когда new() возбуждает исключение. (Иначе – не вызывается.)

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

Можно также перегрузить оператор размещения new[]() и оператор delete[]() для

class Screen { public:

void *operator new[]( size_t );

void *operator new[]( size_t, Screen* ); void operator delete[]( void*, size_t ); void operator delete[]( void*,

Screen* );

// ...

массивов:

};

Оператор new[]() используется в случае, когда в выражении, содержащем new для распределения массива, заданы соответствующие аргументы размещения:

void func( Screen *start ) {

// вызывается Screen::operator new[]( size_t, Screen* )

Screen *ps = new (start) Screen[10]; // ...

}

Если при работе оператора new конструктор возбуждает исключение, то автоматически вызывается соответствующий delete[]().

Упражнение 15.9

class iStack { public:

iStack( int capacity )

: _stack( capacity ), _top( 0 ) {}

// ...

private: int _top;

vatcor< int > _stack;

Объясните, какие из приведенных инициализаций ошибочны:

};

(a)iStack *ps = new iStack(20);

(b)iStack *ps2 = new const

iStack(15);

(c) iStack *ps3 = new iStack[ 100 ];

Упражнение 15.10

class Exercise { public:

Exercise();

~Exercise();

};

Exercise *pe = new

Exercise[20];

Что происходит в следующих выражениях, содержащих new и delete? delete[] ps;

Измените эти выражения так, чтобы вызывались глобальные операторы new() и delete().

Упражнение 15.11

Объясните, зачем разработчик класса должен предоставлять оператор delete().