Перегрузка операций new и delete

Чтобы обеспечить альтернативные варианты управления памятью, можно определять собственные варианты операций new и new[ ] для выделения динамической памяти под объект и массив объектов соответственно, а также операции delete и delete [ ] для ее освобождения.

Правила для данных функций-операций:

· им не требуется передавать параметр типа класса,

· первым параметром функциям new и new[ ] должен передаваться размер объекта типа size_t (при вызове он передается в функции неявным образом),

· они должны определяться с типом возвращаемого значения void*, даже если return возвращает указатель на другие типы (чаще всего на класс),

· операция delete должна иметь тип возврата void и первый аргумент типа void*,

· являются статическими элементами класса.

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

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

Перегрузка операции выделения памяти применяется для экономии памяти, повышения быстродействия программы или для размещения данных в некоторой конкретной области.

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

class Obj {...};

class pObj{

private: Obj *p;

};

При выделении памяти под объект типа pObj с помощью стандартной операции new

pObj *р = new pObj;

фактическое количество байт будет превышать sizeof(pObj), т.к. операция new обычно записывает в начало выделяемой области ее размер (для того, чтобы правильно выполнялась операция delete).

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

Для этого в объект pObj вводится статическое поле headOfFree, в котором хранится указатель на первую свободную ячейку блока для размещения очередного объекта. Неиспользуемые ячейки связываются в список. Чтобы не занимать память под поле связи, используется объединение, с помощью которого одна и та же ячейка используется либо для размещения указателя на объект, либо для связи со следующей свободной ячейкой:

class pObj{

public:

static void * operator new(size_t size);

private:

union{

Obj *p; // Указатель на объект

pObj *next; // Указатель на следующую свободную ячейку

};

static const int BLOCK_SIZE; // Размер блока

// Заголовок списка свободных ячеек:

static pObj *headOfFree;

};

void * pObj::operator new(size_t size){

// Перенаправить запросы неверного количества памяти

// стандартной операции new:

if (size != sizeof(pObj)) return ::operator new(size);

pObj *p = headOfFree; // Указатель на первую свободную ячейку

if (р) headOfFree = р -> next; // Переместить указатель списка

// свободных ячеек

// Если свободной памяти нет, выделяем очередной блок:

else {

pObj *newblock = static_cast<pObj*>

(::operator new(BLOCK_SIZE * sizeof(pObj)));

// Bсe ячейки свободны, кроме первой. Связываем их:

for (int i = 1; i< BLOCK_SIZE – 1; ++i)

newblock[i].next = &newblock[i +1];

newblock[BLOCK_SIZE – l].next = 0;

// Устанавливаем начало списка свободных ячеек:

headOfFree = &newblock[1];

р = newblock;

}

return p; // Возвращаем указатель на выделенную память

}

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

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

pObj *pObj::headOfFree; / / Устанавливается в 0 по умолчанию

const int pObj::BLOCK_SIZE = 1024;

При такой перегрузке операции new должна быть перегружена и операция delete. Стандартная операция delete не найдет в начале объекта верной информации о его размерах, что приведет к неопределенному поведению программы.

Перегруженная операция delete должна добавлять освобожденную ячейку памяти к списку свободных ячеек:

void pObj::operator delete(void * ObjToDie, size_t size){

if (ObjToDie == 0) return;

if (size != sizeof(pObj)){

::operator delete(ObjToDie); return;

}

pObj *p = static_cast<pObj*>(ObjToDie);

p->next = headOfFree;

headOfFree = p; }

В операции delete выполнена проверка соответствия размеров объектов.