Как правильно освободить память, выделенную под размещение нового?

Я читал где-то, что когда вы используете размещение new затем вы должны вызвать деструктор вручную.

рассмотрим следующий код:

   // Allocate memory ourself
char* pMemory = new char[ sizeof(MyClass)];

// Construct the object ourself
MyClass* pMyClass = new( pMemory ) MyClass();

// The destruction of object is our duty.
pMyClass->~MyClass();

насколько я знаю оператор delete обычно вызывает деструктор, а затем освобождает память, верно? Так почему бы нам не использовать ?

delete pMyClass;  //what's wrong with that?

в первом случае мы вынуждены установить pMyClass в nullptr после вызова деструктора как это:

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?

но деструктор не освобождал память, верно? Так это утечка памяти?

Я в замешательстве, вы можете это объяснить?

4 ответов


С помощью new оператор делает две вещи, он вызывает функцию operator new, который выделяет память, а затем использует оператор new для создания объекта в памяти. The delete оператор вызывает деструктор объекта, а затем вызывает operator delete. Да, имена сбивают с толку.

//normal version                   calls these two functions
MyClass* pMemory = new MyClass;    void* pMemory = operator new(sizeof(MyClass));
                                   MyClass* pMyClass = new( pMemory ) MyClass();
//normal version                   calls these two functions
delete pMemory;                    pMyClass->~MyClass();
                                   operator delete(pMemory);

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

однако размещение new предназначено для работы с внутренними буферами (и другими сценариями), где буферы были не выделено operator new, именно поэтому вы не должны звонить operator delete на них.

#include <type_traits>

struct buffer_struct {
    std::aligned_storage<sizeof(MyClass)>::type buffer;
};
int main() {
    buffer_struct a;
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a
    //stuff
    pMyClass->~MyClass(); //can't use delete, because there's no `new`.
    return 0;
}

цель buffer_struct класс должен создавать и уничтожать хранилище любым способом, в то время как main заботится о строительство / уничтожение MyClass обратите внимание, как эти два (почти*) полностью отделены друг от друга.

*мы должны быть уверены, что хранилище должно быть достаточно большим


одна из причин, почему это неправильно:

delete pMyClass;

это то, что вы должны удалить pMemory С delete[] так как это массив:

delete[] pMemory;

вы не можете сделать как выше.

аналогично, вы можете спросить, почему вы не можете использовать malloc() для выделения памяти, оператор new для создания объекта, а затем delete для удаления и освобождения памяти. Причина в том, что вы должны соответствовать malloc() и free(), а не malloc() и delete.

в реальном мире, размещение новые и явные вызовы деструктора почти никогда не используются. Они могут использоваться внутри стандартной реализации библиотеки (или для другого системного программирования, как отмечено в комментариях), но обычные программисты их не используют. Я никогда не использовал такие трюки для производственного кода за многие годы работы на C++.


вам нужно различать delete оператора operator delete. В частности, если вы используете placement new, вы явно вызываете деструктор, а затем вызываете operator delete (а не delete оператора) для освобождения памяти, т. е.

X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);

обратите внимание, что это использует operator delete, что ниже уровня delete оператор и не беспокоится о деструкторах (это по существу немного похоже на free). Сравните это с delete оператор, который внутренне не эквивалент вызова деструктора и вызова operator delete.

стоит отметить, что вам не нужно использовать ::operator new и ::operator delete чтобы выделить и освободить ваш буфер - что касается размещения new, не имеет значения, как буфер возникает / разрушается. Главное-разделить проблемы выделения памяти и времени жизни объекта.

кстати, возможное применение этого было бы в чем-то вроде игры, где вы могли бы хотите выделить большой блок памяти вперед, чтобы тщательно управлять использованием памяти. Затем вы создадите объекты в памяти, которую уже приобрели.

другое возможное использование будет в оптимизированном небольшом распределителе объектов фиксированного размера.


Это, вероятно, легче понять, если вы представляете себе построение несколько объекты MyClass внутри один блок памяти.

в таком случае, это будет что-то вроде:

  1. выделите гигантский блок памяти, используя новый char[10*sizeof (MyClass)] или malloc(10*sizeof(MyClass))
  2. используйте placement new для создания десяти объектов MyClass в этой памяти.
  3. что-то делать.
  4. вызов деструктора каждый из ваших объектов
  5. освободите большой блок памяти с помощью delete[] или free().

Это то, что вы могли бы сделать, если вы писать компилятор или ОС, и т. д.

в этом случае, я надеюсь, понятно, почему вам нужны отдельные шаги" деструктор "и" удалить", потому что нет причин, по которым вы будет звонок удалить. Тем не менее, вы должны освободить память, однако вы обычно это делаете (бесплатно, удалить, ничего не делать для гигантского статического массива, выйти нормально, если массив является частью другого объекта и т. д.), И если вы этого не сделаете, он будет просочился.

также обратите внимание, как сказал Грег, в этом случае вы не можете использовать delete, потому что вы выделили массив с новым [], поэтому вам нужно будет использовать delete[].

также обратите внимание, что вам нужно предположить, что вы не переопределили delete для MyClass, иначе он сделает что-то совершенно другое, что почти наверняка несовместимо с "новый."

поэтому я думаю, что вы не хотите называть "удалить", как вы описываете, но может ли это когда-нибудь работать? Я думаю, что это в основном тот же вопрос, что и "у меня есть два несвязанных типа, у которых нет деструкторов. Могу ли я создать указатель на один тип, а затем удалить эту память с помощью указателя на другой тип? Другими словами, "у моего компилятора есть один большой список всех выделенных вещей или он может делать разные вещи для разных типов".

боюсь, я не уверен. Чтение spec говорит:

5.3.5 ... Если статический тип операнда [оператора удаления] отличается от его динамического типа, то статический тип должен быть базовым класс динамического типа операнда и статический тип должен иметь виртуальный деструктор или поведение не определено.

Я думаю, что это означает: "Если вы используете два несвязанных типа, это не работает (нормально удалять объект класса полиморфно через виртуальный деструктор.)"

Так нет, не делай этого. Я подозреваю, что это может часто работать на практике, если компилятор смотрит только на адрес, а не на тип (и ни один тип не является классом множественного наследования, который исказил бы адрес), но не пытайтесь.