Различные возможные варианты использования ключевых слов new и delete создают значительную путаницу. В C ++ всегда есть два этапа построения динамических объектов: выделение необработанной памяти и построение нового объекта в выделенной области памяти. С другой стороны времени жизни объекта происходит разрушение объекта и освобождение области памяти, в которой находился объект.
Часто эти два шага выполняются одним оператором C ++.
MyObject* ObjPtr = new MyObject;
//...
delete MyObject;
Вместо вышесказанного вы можете использовать функции необработанного выделения памяти в C ++ operator new
и operator delete
, а также явное построение (через размещение new
) и уничтожение для выполнения эквивалентных шагов.
void* MemoryPtr = ::operator new( sizeof(MyObject) );
MyObject* ObjPtr = new (MemoryPtr) MyObject;
// ...
ObjPtr->~MyObject();
::operator delete( MemoryPtr );
Обратите внимание на то, что не используется приведение, и в выделенной области памяти создается только один тип объекта. Использование чего-то вроде new char[N]
в качестве способа выделения необработанной памяти технически некорректно, поскольку логически char
объекты создаются во вновь выделенной памяти. Я не знаю ни одной ситуации, когда это не «просто работает», но стирает различие между необработанным выделением памяти и созданием объектов, поэтому я советую против этого.
В этом конкретном случае нет никакой выгоды, которая может быть получена при разделении двух шагов delete
, но вам необходимо вручную контролировать начальное распределение. Приведенный выше код работает в сценарии «все работает», но он утечет необработанную память в случае, когда конструктор MyObject
выдает исключение. Хотя это можно было бы уловить и решить с помощью обработчика исключений в точке выделения, вероятно, лучше предоставить пользовательский оператор new, чтобы вся конструкция могла быть обработана с помощью выражения new размещения.
class MyObject
{
void* operator new( std::size_t rqsize, std::size_t padding )
{
return ::operator new( rqsize + padding );
}
// Usual (non-placement) delete
// We need to define this as our placement operator delete
// function happens to have one of the allowed signatures for
// a non-placement operator delete
void operator delete( void* p )
{
::operator delete( p );
}
// Placement operator delete
void operator delete( void* p, std::size_t )
{
::operator delete( p );
}
};
Здесь есть пара тонких моментов. Мы определяем размещение класса новым, так что мы можем выделить достаточно памяти для экземпляра класса плюс некоторые пользовательские отступы. Поскольку мы делаем это, нам нужно обеспечить соответствующее удаление размещения, чтобы в случае успешного распределения памяти, но при сбое конструкции, выделенная память автоматически освобождалась. К сожалению, подпись для нашего удаления места размещения соответствует одной из двух разрешенных подписей для удаления без размещения, поэтому нам необходимо предоставить другую форму удаления без размещения, чтобы наше реальное удаление места размещения рассматривалось как удаление места размещения. (Мы могли бы обойти это, добавив дополнительный фиктивный параметр как к нашему новому месту размещения, так и к удалению места размещения, но это потребовало бы дополнительной работы на всех вызывающих сайтах.)
// Called in one step like so:
MyObject* ObjectPtr = new (padding) MyObject;
Используя одно новое выражение, мы теперь гарантируем, что память не будет вытекать, если какая-либо часть нового выражения выбросит.
На другом конце времени жизни объекта, поскольку мы определили оператор delete (даже если бы мы этого не сделали, память для объекта изначально была получена от глобального оператора new в любом случае), следующий способ является правильным способом уничтожения динамически создаваемый объект.
delete ObjectPtr;
Резюме!
Не смотрите! operator new
и operator delete
имеют дело с необработанной памятью, размещение новых может создавать объекты в необработанной памяти. Явное приведение от void*
к указателю объекта обычно является признаком чего-то логически неправильного, даже если оно «просто работает».
Мы полностью проигнорировали new [] и delete []. Эти объекты переменного размера не будут работать в массивах в любом случае.
Placement new позволяет новому выражению не просачиваться, новое выражение по-прежнему оценивается как указатель на объект, который нужно уничтожить, и память, которая требует освобождения. Использование некоторых типов интеллектуальных указателей может помочь предотвратить другие типы утечек. С другой стороны, мы допустили, чтобы правильный delete
был правильным способом, чтобы большинство стандартных интеллектуальных указателей работали.