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

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

730

 

 

class ScreenPtr {

 

 

 

 

 

 

// объявления не членов

// префиксные операторы

 

 

friend Screen& operator++( Screen & );

 

 

friend Screen& operator--( Screen & );

 

 

 

friend Screen& operator++( Screen &, int); // постфиксные операторы

 

 

friend Screen& operator--( Screen &, int);

 

 

public:

 

 

 

// определения членов

 

 

 

};

 

 

 

 

 

 

 

Упражнение 15.7

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

Упражнение 15.8

С помощью ScreenPtr можно представить указатель на массив объектов класса Screen. Модифицируйте перегруженные operator*() и operator->() (см. раздел 15.6) так,

чтобы указатель ни при каком условии не адресовал элемент перед началом или за концом массива. Совет: в этих операторах следует воспользоваться новыми членами size

и offset.

15.8. Операторы new и delete

По умолчанию выделение объекта класса из хипа и освобождение занятой им памяти выполняются с помощью глобальных операторов new() и delete(), определенных в стандартной библиотеке C++. (Мы рассматривали эти операторы в разделе 8.4.) Но класс может реализовать и собственную стратегию управления памятью, предоставив одноименные операторы-члены. Если они определены в классе, то вызываются вместо

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

Определим операторы new() и delete() в нашем классе Screen.

Оператор-член new() должен возвращать значение типа void* и принимать в качестве первого параметра значение типа size_t, где size_t это typedef, определенный в

class Screen { public:

void *operator new( size_t ); // ...

системном заголовочном файле <cstddef>. Вот его объявление:

};

Когда для создания объекта типа класса используется new(), компилятор проверяет, определен ли в этом классе такой оператор. Если да, то для выделения памяти под объект вызывается именно он, в противном случае глобальный оператор new(). Например,

следующая инструкция

Screen *ps = new Screen;

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

731

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

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

С помощью оператора разрешения глобальной области видимости можно вызвать глобальный new(), даже если в классе Screen определена собственная версия:

Screen *ps = ::new Screen;

Оператор delete(), являющийся членом класса, должен иметь тип void, а в качестве первого параметра принимать void*. Вот как выглядит его объявление

class Screen { public:

void operator delete( void * );

для Screen:

};

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

инструкция

delete ps;

освобождает память, занятую объектом класса Screen, на который указывает ps. Поскольку в Screen есть оператор-член delete(), то применяется именно он. Параметр оператора типа void* автоматически инициализируется значением ps.

Добавление delete() в класс или его удаление оттуда никак не сказываются на пользовательском коде. Вызов delete выглядит одинаково как для глобального оператора, так и для оператора-члена. Если бы в классе Screen не было собственного оператора delete(), то обращение осталось бы правильным, только вместо оператора- члена вызывался бы глобальный оператор.

С помощью оператора разрешения глобальной области видимости можно вызвать глобальный delete(), даже если в Screen определена собственная версия:

::delete ps;

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

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

732

Оператор delete(), определенный для типа класса, может содержать два параметра вместо одного. Первый параметр по-прежнему должен иметь тип void*, а второй

class Screen { public:

//заменяет

//void operator delete( void * );

void operator delete( void *, size_t );

предопределенный тип size_t (не забудьте включить заголовочный файл <cstddef>):

};

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

Рассмотрим реализацию операторов new() и delete() в классе Screen более детально. В

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

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

class Screen { public:

void *operator new( size_t );

void operator delete( void *, size_t ); // ...

private:

Screen *next;

static Screen *freeStore; static const int screenChunk;

Screen, нам понадобится третий член next:

};

Вот одна из возможных реализаций оператора new() для класса Screen:

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

733

#include "Screen.h" #include <cstddef>

//статические члены инициализируются

//в исходных файлах программы, а не в заголовочных файлах

Screen *Screen::freeStore = 0; const int Screen::screenChunk = 24;

void *Screen::operator new( size_t size )

{

Screen *p;

if ( !freeStore ) {

//связанный список пуст: получить новый блок

//вызывается глобальный оператор new

size_t chunk = screenChunk * size; freeStore = p =

reinterpret_cast< Screen* >( new char[ chunk ] );

// включить полученный блок в список for ( ;

p != &freeStore[ screenChunk - 1 ]; ++p )

p->next = p+1; p->next = 0;

}

p = freeStore;

freeStore = freeStore->next; return p;

}

void Screen::operator delete( void *p, size_t )

{

//вставить "удаленный" объект назад,

//в список свободных

( static_cast< Screen* >( p ) )->next = freeStore; freeStore = static_cast< Screen* >( p );

Авот реализация оператора delete():

}

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

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