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), называется оператором new с размещением или размещающим оператором new (placement new). Приведенный выше оператор new как раз и является таковым. Особенно полезным бывает размещающий оператор new, для которого вторым аргументом служит указатель на область памяти, где объект должен быть сконструирован. Этот оператор new выглядит так:
void *operator new(std::size_t, void *pMemory) throw(); // “размещающий new”
Эта версия new является частью стандартной библиотеки C++, и вы получаете к ней доступ, включая в исходный текст директиву #include . Кстати говоря, такой оператор 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, который выполняет протоколирование:
Читать дальше
Конец ознакомительного отрывка
Купить книгу