Перегрузка операторов new и delete
Bar больше Foo, поэтому Foo: operator new перепоручает работу глобальному оператору new. Но когда подходит время освобождать память, происходит путаница. Размер, передаваемый Foo: operator delete, основан на предположении компилятора относительно настоящего типа, а оно может оказаться неверным. В данном случае было указано компилятору, что это Foo, а не Bar. Чтобы справиться с затруднениями… Читать ещё >
Перегрузка операторов new и delete (реферат, курсовая, диплом, контрольная)
Простой список свободной памяти
Рассмотрим пример 2.1, где оператор delete включает освобождаемые блоки в список свободной памяти. Оператор new сначала пытается выделить блок из списка и обращается к глобальному оператору new лишь в том случае, если список свободной памяти пуст.
Пример 2.1 Оператор delete включает освобождаемые блоки в список свободной памяти.
class Foo {.
private:
struct FreeNode {.
FreeNode* next;
};
static FreeNode* fdFreeList;
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;
}.
};
Пример 2.1 демонстрирует общие принципы перегрузки операторов new и delete для конкретного класса. Оператор new получает один аргумент, объем выделяемого блока, и возвращает адрес выделенного блока. Аргументом оператора delete является адрес освобождаемой области. Объявлять их виртуальными нельзя.
При вызове оператора new, компилятор знает, с каким классом он имеет дело, поэтому v-таблица ему нужна. При вызове оператора delete деструктор определяет, какому классу этот оператор должен принадлежать. Если нужно гарантировать, что будет вызываться оператор delete производного класса, то виртуальным нужно сделать деструктор, а не оператор delete.
Перегрузки будут унаследованы производными класса Foo, поэтому это повлияет и на процесс выделения/освобождения памяти в них. На практике нередко создается абстрактный базовый класс, который не делает почти ничего (как и в приведенном примере) и используется только для создания классов с данной схемой управления памятью.
Пример 2.2 Наследование Bar.
class Bar: public Baseclass, public Foo { … };
В примере 2.2, Bar наследует все базовые характеристики типа Baseclass, а нестандартное управление памятью — от Foo.
Предполагается, что экземпляр Foo содержит не меньше байт, чем Foo: FreeNode*. Для классов вроде этого, не имеющего переменных и виртуальных функций, этого гарантировать нельзя. Он будет иметь определенный размер (во многих компиляторах — два байта), чтобы объекты обладали уникальным адресом, но по количеству байт он может быть меньше указателя на FreeNode. Нужно гарантировать, что размер Foo не меньше размера указателя — для этого нужно включить в него v-таблицу или хотя бы переменные, дополняющие его до размера указателя.
Другая проблема заключается в том, что приведенный фрагмент не работает с производными классами, в которых добавляются новые переменные. Показана иерархия классов в примере 2.3.
Пример 2.3 Иерархия классов.
class Bar: public Foo {.
private:
int x;
};
Каждый экземпляр Bar по крайней мере на пару байт больше, чем экземпляр Foo. Если удалить экземпляр Foo, а затем попытаться немедленно выделить память для экземпляра Bar — выделенный блок оказывается на ту же на пару байт короче. Возможное решение в примере 2.4 — сделать оператор new так, чтобы он перехватывал только попытки выделения правильного количества байт.
Пример 2.4 Перехват попытки выделения правильного количества байт.
class Foo {.
public:
void* operator new (size_t bytes).
if (bytes ≠ sizeof (Foo) || fgFreeList == NULL).
return :operator new (bytes);
FreeNode* node = fgFreeList;
FgFreeList = fgFreeList->next;
Return node;
}.
};
Процесс освобождения необходимо изменить в соответствии с этой стратегией. Альтернативная форма оператора delete в примере 2.5 имеет второй аргумент — количество освобождаемых байт:
Пример 2.5 Альтернативная форма оператора delete.
class Foo {.
public:
void* operator new (size_t bytes);
void operator delete (void* space, size_t bytes).
{.
if (bytes ≠ sizeof (Foo)).
:operator delete (space);
((FreeNode*)space)->next = fgFreeList;
fgFreeList = (FreeNode*)space;
}.
};
Теперь в список будут заноситься только настоящие Foo и производные классы, совпадающие по размеру.
Foo* foo = new Bar;
delete foo;
Bar больше Foo, поэтому Foo: operator new перепоручает работу глобальному оператору new. Но когда подходит время освобождать память, происходит путаница. Размер, передаваемый Foo: operator delete, основан на предположении компилятора относительно настоящего типа, а оно может оказаться неверным. В данном случае было указано компилятору, что это Foo, а не Bar. Чтобы справиться с затруднениями, необходимо знать точную последовательность уничтожения, возникающую в операторах вида delete foo;. Сначала вызываются деструкторы, начиная с производного класса, и далее вверх по цепочке. Затем оператор delete вызывается кодом, окружающим деструктор производного класса. Это означает, что проблема возникает только для невиртуальных деструкторов. Если деструктор является виртуальным, аргумент размера в операторе delete всегда будет правильным.
Фрагмент, в примере 2.6, всегда будет правильно работать на компиляторах, использующих v-таблицы.
Пример 2.6 Фрагмент, работающий с использование v-таблицы.
class Foo {.
private:
struct FreeNode {.
FreeNode* next;
};
static FreeNode* fdFreeList;
public:
virtual ~Foo () {}.
void* operator new (size_t bytes).
{.
if (bytes ≠ sizeof (Foo) || fgFreeList == NULL).
return :operator new (bytes);
FreeNode* node = fgFreeList;
FgFreeList = fgFreeList->next;
return node;
}.
void operator delete (void* space, size_t bytes).
{.
if (bytes ≠ sizeof (Foo)).
return :operator delete (space);
((FreeNode*)space)->next = fgFreeList;
fgFreeList = (FreeNode*)space;
}.
};
Указатель v-таблицы гарантирует, что каждый Foo по крайней мере не меньше указателя на следующий элемент списка (FreeNode*), а виртуальный деструктор обеспечивает правильность размера, передаваемого оператору delete.