Подтвердить что ты не робот

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

Я читал, что когда вы используете размещение 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?

delete pMyClass;  //what wrong with that?

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

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

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

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

4b9b3361

Ответ 1

Использование оператора new выполняет две функции: вызывает функцию operator new, которая выделяет память, а затем использует новое размещение, чтобы создать объект в этой памяти. Оператор 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, должен быть установлен соответствующий 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 no `new`.
    return 0;
}

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

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

Ответ 2

По одной причине это неверно:

delete pMyClass;

заключается в том, что вы должны удалить pMemory с помощью delete[], так как это массив:

delete[] pMemory;

Вы не можете обойти оба.

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

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

Ответ 3

Вам нужно различать оператор delete и operator delete. В частности, если вы используете новое место размещения, вы явно вызываете деструктор, а затем вызываете 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, не имеет значения, как создается или уничтожается буфер, Главное - отделить проблемы выделения памяти и времени жизни объекта.

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

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

Ответ 4

Вероятно, проще понять, если вы планируете создать несколько объектов MyClass в блоке памяти one.

В этом случае он будет выглядеть примерно так:

  • Выделите гигантский блок памяти с помощью нового char [10 * sizeof (MyClass)] или malloc (10 * sizeof (MyClass))
  • Используйте новое размещение для создания десяти объектов MyClass в этой памяти.
  • Сделайте что-нибудь.
  • Вызвать деструктор каждого из ваших объектов
  • Отмените большой блок памяти, используя delete [] или free().

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

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

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

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

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

Боюсь, я не уверен. Читая спецификацию, он говорит:

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

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

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