Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

Widget *pw = new (std::cerr) Widget; // как раньше, но теперь никаких

// утечек


то автоматически будет вызван соответственный размещающий оператор delete, так что Widget гарантирует, что никаких утечек памяти по этой причине не будет.

Посмотрим, что произойдет, если никаких исключений нет (как обычно и бывает), а в пользовательском коде присутствует явный вызов delete:


delete pw; // вызов обычного оператора delete


Как сказано в комментарии, здесь вызывается обычный оператор delete, а не размещающая версия. Размещающий delete вызывается, только если возбуждает исключение конструктор, следующий за вызовом размещающего new. Если delete применяется к указателю (в примере выше – pw), то версия 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);}

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже