Посмотрим, что произойдет, если никаких исключений нет (как обычно и бывает), а в пользовательском коде присутствует явный вызов delete:
delete pw; // вызов обычного оператора delete
Как сказано в комментарии, здесь вызывается обычный оператор delete, а не размещающая версия. Размещающий delete вызывается,
Это значит, что для предотвращения всех утечек памяти, ассоциированных с размещающей версией new, вы должны также предоставить и обычный оператор delete (на случай, если в конструкторе не возникнет исключений), и размещающую версию с теми же дополнительными аргументами, что и у размещающего new (если таковой имеется). Поступайте так, и вы никогда не потеряете сон из-за неуловимых утечек памяти. Ну, по крайней мере, из-за утечек памяти
Кстати, поскольку имена функций-членов скрывают одноименные функции в объемлющих контекстах (см. правило 33), вы должны быть осторожны, чтобы избежать того, что операторы new уровня класса скроют другие версии new (в том числе обычные), на которые рассчитывают пользователи. Например, если у вас есть базовый класс, в котором объявлена только размещающая версия оператора new, пользователи обнаружат, что обычная форма new стала недоступной:
class Base {
public:
...
static void *operator new(std::size_t size, // скрывает обычные
std::ostream& logStream) // глобальные формы
throw(std::bad_alloc);
...
};
Base *pb = new Base; // ошибка! Обычная форма
// оператора new скрыта
Base *pb = new (std::cerr)Base; // правильно, вызывается
// размещающий new из Base
Аналогично оператор new в производных классах скрывает и глобальную, и унаследованную версии оператора new:
class Derived: public Base {
public:
...
static void *operator new(std::size_t size) // переопределяет
throw(std::bad_alloc); // обычную форму new
...
};
Derived *pd = new (std::cerr)Derived; // ошибка! заменяющая
// форма теперь скрыта
Derived *pd = new Derived; // правильно, вызывается
// оператор new из Derived
В правиле 33 достаточно подробно рассмотрен этот вид сокрытия имен в классе, но при написании функций распределения памяти нужно помнить, что по умолчанию C++ представляет следующие формы оператора new в глобальной области видимости:
void operator new(std::size_t) throw(bad_alloc); // обычный new
void operator new(std::size_t, void*) throw(bad_alloc); // размещающий new
void operator new(std::size_t, // new, не возбуждающий
const std::nothrow_t&) throw(); // исключений –
// см. правило 49
Если вы объявляете любой оператор new в классе, то тем самым скрываете все эти стандартные формы. Убедитесь, что вы сделали их доступными в дополнение к любым специальным формам new, объявленным вами в классе, если только в ваши намерения не входит запретить использование этих форм пользователям класса. И для каждого оператора new, к которому вы даете доступ, должен быть также предоставлен соответствующий оператор delete. Если вы хотите, чтобы эти функции вели себя обычным образом, просто вызывайте соответствующие глобальные их версии из своих функций.
Самый простой способ – создать базовый класс, содержащий все нормальные формы new и delete:
class StandardNewDeleteForms {
public:
// нормальные new/delete
static void *operator new(std::size_t size) throw(bad_alloc)
{ return ::operator new(size);}
static void operator delete(void *pMemory) throw()
{ ::operator delete(pMemory);}
// размещающие new/delete
static void *operator new(std::size_t size, void *ptr) throw(bad_alloc)
{ return ::operator new(size, ptr);}