Чтение онлайн

на главную - закладки

Жанры

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

Майерс Скотт

Шрифт:

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

Эта версия new является частью стандартной библиотеки C++, и вы получаете к ней доступ, включая в исходный текст директиву #include <new>. Кстати говоря, такой оператор new используется в реализации класса vector для создания объектов в выделенной для вектора памяти. Это также первоначальная версия оператора new с размещением; именно она и получила название «placement new». Таким образом, сам термин «размещающий new» перегружен. Обычно, когда говорят о размещающем new, имеют в виду эту

конкретную функцию: оператор new, принимающий дополнительный аргумент типа void*. Реже так говорят о любой другой версии new, принимающей дополнительные аргументы. Обычно контекст исключает противоречивые толкования, но важно понимать, что общий термин «размещающий new» означает любую версию new, принимающую дополнительные аргументы, поскольку выражение «размещающий delete» или «delete с размещением» (которое мы сейчас обсудим) происходит от него.

Но вернемся к объявлению класса Widget, которое я не одобрил. Проблема в том, что этот класс открывает возможность утечки памяти. Рассмотрим следующий пользовательский код, который протоколирует информацию о выделении памяти в поток cerr при динамическом создании объектов Widget:

Widget *pw = new (std::cerr) Widget; // вызвать оператор new, передав cerr

// в качестве параметра типа ofstream;

// это ведет к утечке памяти в случае,

// когда конструктор Widget возбуждает

// исключение

Если выделение памяти прошло успешно, но конструктор Widget возбуждает исключение, то исполняющая система отвечает за освобождение той памяти, которую успел выделить оператор new. Исполняющая система понятия не имеет, как работает вызванная версия оператора new, поэтому не может отменить результат операции самостоятельно. Вместо этого исполняющая система ищет версию оператора delete, которая принимает то же количество аргументов того же типа, что и new, и если находит его, то вызывает. В данном случае оператор new принимает дополнительный аргумент типа ostream&, поэтому соответствующий оператор delete должен иметь следующую сигнатуру:

void operator delete(void *, std::ostream&) throw;

По аналогии с размещающими версиями new версии оператора delete, которые принимают дополнительные параметры, называются размещающими delete. Но в классе Widget не объявлена размещающая версия оператора delete, поэтому исполняющая система не знает, как отменить то, что сделал размещающий new. В результате она не делает ничего. В этом примере никакой оператор delete не вызывается, если конструктор Widget возбуждает исключение!

Правило простое: если оператору new с дополнительными аргументами не соответствует оператор delete с такими же аргументами, то никакой delete не вызывается в случае необходимости отменить выделение памяти, выполненное new. Чтобы избежать утечек памяти в приведенном выше коде, Widget должен объявить размещающий оператор delete, который соответствует размещающему оператору new, который выполняет протоколирование:

class Widget {

public:

...

static void *operator new(std:size_t size, std::ostream& logStream)

throw(std::bad_alloc);

static void operator delete(void *pMemory) throw;

static void operator delete(void *pMemory, std::ostream& logStream)

throw;

...

};

С

этим изменением, если конструктор Widget возбудит исключение в предложении

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; // ошибка! заменяющая

Поделиться:
Популярные книги

Эпоха Опустошителя. Том I

Павлов Вел
1. Вечное Ристалище
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Эпоха Опустошителя. Том I

Проблема майора Багирова

Майер Кристина
1. Спецназ
Любовные романы:
современные любовные романы
6.60
рейтинг книги
Проблема майора Багирова

Законы Рода. Том 13

Андрей Мельник
13. Граф Берестьев
Фантастика:
аниме
фэнтези
5.00
рейтинг книги
Законы Рода. Том 13

Газлайтер. Том 15

Володин Григорий Григорьевич
15. История Телепата
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Газлайтер. Том 15

О, Путник!

Арбеков Александр Анатольевич
1. Квинтет. Миры
Фантастика:
социально-философская фантастика
5.00
рейтинг книги
О, Путник!

Прометей: каменный век

Рави Ивар
1. Прометей
Фантастика:
альтернативная история
6.82
рейтинг книги
Прометей: каменный век

Её (мой) ребенок

Рам Янка
Любовные романы:
современные любовные романы
6.91
рейтинг книги
Её (мой) ребенок

Идеальный мир для Лекаря 8

Сапфир Олег
8. Лекарь
Фантастика:
юмористическое фэнтези
аниме
7.00
рейтинг книги
Идеальный мир для Лекаря 8

Прометей: каменный век II

Рави Ивар
2. Прометей
Фантастика:
альтернативная история
7.40
рейтинг книги
Прометей: каменный век II

Цвет сверхдержавы - красный. Трилогия

Симонов Сергей
Цвет сверхдержавы - красный
Фантастика:
попаданцы
альтернативная история
8.06
рейтинг книги
Цвет сверхдержавы - красный. Трилогия

Болтливый мертвец

Фрай Макс
7. Лабиринты Ехо
Фантастика:
фэнтези
9.41
рейтинг книги
Болтливый мертвец

На границе империй. Том 9. Часть 2

INDIGO
15. Фортуна дама переменчивая
Фантастика:
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 9. Часть 2

Истребители. Трилогия

Поселягин Владимир Геннадьевич
Фантастика:
альтернативная история
7.30
рейтинг книги
Истребители. Трилогия

Лишняя дочь

Nata Zzika
Любовные романы:
любовно-фантастические романы
8.22
рейтинг книги
Лишняя дочь