Это все соглашения, которым вы должны следовать при написании оператора new[]. Что касается оператора delete, то с ним все проще. Почти все, что вам нужно знать, – это то, что C++ гарантирует безопасность освобождения памяти по нулевому адресу, поэтому и вы должны предоставить такую гарантию. Вот псевдокод для оператора delete, не являющегося членом класса:

void operator delete(void *rawMemory) throw()

{

if(rawMemory == 0) return; // ничего не делать, если передан нулевой

// указатель

освободить память, на которую указывает rawMemory

;

}

Версия этой функции, являющаяся членом класса, также проста, за исключением того, что нужно проверить размер того, что вы собираетесь освобождать. Предполагая, что оператор new, определенный в классе, передает запрос на выделение «неправильного» количества байтов глобальному::operator new, вы также должны передать информацию о «неверном» размере функции::operator delete:

class Base { // то же, что и раньше, но добавлено

public: // объявление operator delete

static void *operator new(std::size_t size) throw(std::bad_alloc);

static void operator delete(void *rawMemory, std::size_t size) throw();

...

};

void Base::operator delete(void *rawMemory, std::size_t size) throw()

{

if(rawMemory == 0) return; // проверка на нулевой указатель

if(size != sizeof(Base)) { // если размер «неверный»,

::operator delete(rawMemory); // вызвать стандартный оператор

return; // delete для обработки запроса

}

освободить память, на которую указывает rawMemory

;

return;

}

Интересно, что значение типа size_t, которое C++ передает оператору delete, может быть неправильным, если удаляется объект, производный от класса, в котором нет виртуального деструктора. Одного этого уже достаточно, чтобы требовать от базового класса наличия виртуального деструктора, но в правиле 7 описана и другая, более существенная причина. Пока просто отметьте, что если вы опустили виртуальный деструктор в базовом классе, то функция operator delete может работать неправильно.

Что следует помнить

• Оператор new должен содержать бесконечный цикл, который пытается выделить память, должен вызывать функцию- обработчик new, если не удается удовлетворить запрос на выделение памяти, и должен обрабатывать запрос на выделение нуля байтов. Версии оператора new уровня класса должны обрабатывать запросы на выделение блоков большего размера, чем ожидается.

• Оператор delete не должен ничего делать при передаче ему нулевого указателя. Версии оператора delete уровня класса должны обрабатывать запросы на освобождение блоков, которые больше, чем ожидается.

Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete

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

Widget *pw = new Widget;

то вызываются две функции: оператор new, чтобы выделить память, и конструктор Widget по умолчанию.

Предположим, что первый вызов завершился успешно, а второй возбудил исключение. В этом случае необходимо отменить выделение памяти, выполненное на шаге 1. В противном случае мы получим утечку памяти. Пользовательский код не может освободить память, потому что конструктор Widget возбудил исключение и pw ничего так и не было присвоено. Следовательно, пользователь так и не получил указатель на память, которая должна быть освобождена. Поэтому ответственность за отмену шага 1 возлагается на систему времени исполнения C++.

Исполняющая система рада бы вызвать оператор delete, соответствующий использованному на шаге 1 оператору new, но сделать это может лишь тогда, когда знает, какой именно вариант оператора delete – а их много – нужно вызвать. Это не проблема, если вы пользуетесь формами new и delete с обычными сигнатурами, потому что обычный оператор new:

void *operator new(std::size_t size) throw(std::bad_alloc);

соответствует обычному оператору delete:

void operator delete(void *rawMemory) throw(); // обычная сигнатура

// в глобальной области

// видимости

void operator delete(void *rawMemory, // наиболее распространенная

std::size_t size) throw(); // сигнатура в области

// видимости класса

Если вы пользуетесь только обычными формами new и delete, то исполняющая система легко найдет тот вариант delete, который знает, как отменить действие, выполненное оператором new. Проблема поиска правильного варианта delete возникает тогда, когда вы объявляете необычные формы оператора new – такие, которые принимают дополнительные параметры.

Например, предположим, что вы написали оператор new уровня класса, который требует задания потока ofstream, куда должна выводиться отладочная информация о выделении памяти, и вместе с ним написали также обычный оператор delete уровня класса:

class Widget {

public:

...

static void *operator new(std:size_t size, // необычная

std::ostream& logStream) // форма new

throw(std::bad_alloc);

static void operator delete(void *pMemory, // обычная

std:size_t size) throw(); // форма delete

// уровня класса

...

};

Такое решение наверняка приведет к ошибкам, но чтобы понять, почему это так, придется познакомиться с некоторыми терминами.

Функция operator new, принимающая дополнительные параметры (помимо обязательного аргумента size_t), называется оператором new с размещением или размещающим оператором new (placement new). Приведенный выше оператор new как раз и является таковым. Особенно полезным бывает размещающий оператор new, для которого вторым аргументом служит указатель на область памяти, где объект должен быть сконструирован. Этот оператор new выглядит так:

void *operator new(std::size_t, void *pMemory) throw(); // “размещающий

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату