194
public:
void* operator new(size_t bytes)
{
if (fgFreeList == NULL)
return
::operator
new(bytes);
FreeNode* node = fgFreeList;
FgFreeList
=
fgFreeList->next;
return
node;
}
void operator delete(void* space)
{
((FreeNode*)space->next
=
fgFreeList;
fgFreeList
=
(FreeNode*)space;
}
};
Как вы вскоре убедитесь, приведенный фрагмент неполон, однако он демонстрирует общие принципы
перегрузки операторов
new
и
delete
для конкретного класса. Оператор new получает один аргумент,
объем
выделяемого блока, и возвращает адрес выделенного блока. Аргументом оператора
delete
является адрес освобождаемой области. Не пытайтесь объявлять их виртуальными; компилятор лишь
посмеется над вами. При вызове оператора
new
компилятор
точно знает, с каким классом он имеет
дело, поэтому v-таблица ему не нужна. При вызове оператора
delete
деструктор определяет, какому
классу этот оператор должен принадлежать. Если вы хотите гарантировать,
что будет вызываться
оператор
delete
производного класса, то виртуальным нужно сделать деструктор, а не оператор
delete
. Перегрузки будут унаследованы
производными класса
Foo
, поэтому это повлияет и на
процесс выделения/освобождения памяти в них. На практике нередко создается абстрактный базовый
класс, который не делает почти ничего (как и в приведенном примере) и используется только для
создания классов с данной схемой управления памятью.
class Bar : public Baseclass, public Foo { ... };
Здесь
Bar
наследует все
базовые характеристики типа
Baseclass
, а нестандартное управление
памятью — от
Foo
.
Ограничения минимального размера
Перед тем как хвататься за компилятор, необходимо привести в порядок показанный фрагмент. Во-
первых,
предполагается, что
экземпляр
Foo
содержит по крайней мере не меньше байт, чем
Foo::FreeNode*
. Для классов вроде нашего, не имеющего переменных и виртуальных функций, этого
гарантировать нельзя. Он будет иметь определенный размер (во многих компиляторах — два байта),
чтобы объекты обладали уникальным адресом, но по количеству байт он может быть меньше указателя
на
FreeNode
. Мы
должны гарантировать, что размер
Foo
не меньше размера указателя — для этого
нужно включить в него v-таблицу или хотя бы переменные, дополняющие его до размера указателя.
Производные классы с добавленными переменными
Другая проблема заключается в том, что приведенный фрагмент не работает с производными классами,
в которых добавляются новые переменные. Рассмотрим следующую иерархию классов.
class Bar : public Foo {
private:
int
x;
};
Каждый экземпляр
Bar
по крайней мере на пару байт больше, чем экземпляр
Foo
.
Если удалить
экземпляр
Foo
, а затем попытаться немедленно выделить память для экземпляра
Bar
… караул!
Выделенный блок оказывается на ту же на пару байт короче. Возможное силовое решение — сделать
оператор
new
достаточно умным для того, чтобы он перехватывал
только попытки выделения
правильного количества байт. Позднее будут рассмотрены и более изящные решения.