Как правильно освободить память, выделенную путем размещения новых? - PullRequest
21 голосов
/ 19 января 2012

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

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

   // 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's wrong with that?

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

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

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

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

Ответы [ 4 ]

37 голосов
/ 19 января 2012

Использование оператора new делает две вещи, вызывает функцию operator new, которая выделяет память, а затем использует размещение 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.Always.

Однако, размещение 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 заботится о строительстве / уничтожении * 1021.*, обратите внимание, что они (почти *) полностью отделены друг от друга.

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

9 голосов
/ 19 января 2012

Одна из причин, по которой это неверно:

delete pMyClass;

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

delete[] pMemory;

Вы не можете сделатьоба из вышеперечисленных.

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

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

4 голосов
/ 19 января 2012

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

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

Еще одно возможное использование - оптимизированный распределитель объектов небольшого размера.

3 голосов
/ 19 января 2012

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

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

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

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

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

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

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

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

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

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

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

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

...