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(); // “размещающий new”