Это все соглашения, которым вы должны следовать при написании оператора new[]. Что касается оператора delete, то с ним все проще. Почти все, что вам нужно знать, – это то, что C++ гарантирует безопасность освобождения памяти по нулевому адресу, поэтому и вы должны предоставить такую гарантию. Вот псевдокод для оператора delete, не являющегося членом класса:
void operator delete(void *rawMemory) throw()
{
if(rawMemory == 0) return; // ничего не делать, если передан нулевой
// указатель
;
}
Версия этой функции, являющаяся членом класса, также проста, за исключением того, что нужно проверить размер того, что вы собираетесь освобождать. Предполагая, что оператор 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 для обработки запроса
}
;
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), называется
void *operator new(std::size_t, void *pMemory) throw(); // “размещающий