Помощь в написании студенческих работ
Антистрессовый сервис

Перегрузка операторов 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.

Показать весь текст
Заполнить форму текущей работой