Освобождается ли память, когда вызывается деструктор или когда вызывается `delete`? - PullRequest
15 голосов
/ 23 августа 2011

Предположим, у вас есть объект class Fool.

class Fool
{
    int a,b,c;
    double* array ;
    //...
    ~Fool()
    {
        // destroys the array..
        delete[] array ;
    }
};


Fool *fool = new Fool() ;

Теперь, Я знаю, что вы не должны , но какой-то дурак все равно вызывает деструктор на fool. fool->~Fool();.

Означает ли это, что fool освобождает память (то есть, a, b, c недопустимы) или означает ли это только то, что происходит в функции ~Fool() (т. Е. Массив удаляется только?)

Итак, я предполагаю, что мой вопрос в том, является ли деструктор просто еще одной функцией , которая вызывается, когда delete вызывается для объекта, или она делает больше?

Ответы [ 5 ]

30 голосов
/ 23 августа 2011

Если вы пишете

fool->~Fool();

Вы заканчиваете время жизни объекта, которое вызывает деструктор и восстанавливает внутренний массив array.Однако память, в которой хранится объект, не освобождается, что означает, что если вы хотите вернуть объект к жизни, используя новое размещение:

new (fool) Fool;

, вы можете сделать это.

В соответствии со спецификацией чтение или запись значений полей fool после явного вызова деструктора приводит к неопределенному поведению, поскольку время жизни объекта закончилось, но память, содержащая объект, все равно должна быть выделенаи вам нужно будет освободить его, вызвав operator delete:

fool->~Fool();
operator delete(fool);

Причина использования operator delete вместо простого написания

delete fool;

заключается в том, что последний имеет неопределенное поведение,потому что время жизни fool уже закончилось.Использование необработанной процедуры освобождения operator delete гарантирует, что память восстанавливается, не пытаясь что-либо сделать, чтобы закончить время жизни объекта.

Конечно, если память для объекта не была взята из new (возможно,он распределяется по стеку, или, возможно, вы используете собственный распределитель), тогда вам не следует использовать operator delete для его освобождения.Если бы вы сделали, вы бы в конечном итоге с неопределенным поведением (снова!).Это, кажется, повторяющаяся тема в этом вопросе.: -)

Надеюсь, это поможет!

8 голосов
/ 23 августа 2011

Именно так и поступает вызов деструктора, он вызывает деструктор.Не больше, не меньше.Распределение отделено от конструкции, а освобождение - от разрушения.

Типичная последовательность такова:

1. Allocate memory
2. Construct object
3. Destroy object  (assuming no exception during construction)
4. Deallocate memory

Фактически, если вы выполните это вручную, вы получите чтобы вызвать деструктор самостоятельно:

void * addr = ::operator new(sizeof(Fool));
Fool * fp = new (addr) Fool;
fp->~Fool();
::operator delete(addr);

Автоматический способ написания этого, конечно, Fool * fp = new Fool; delete fp;.Выражение new вызывает для вас распределение и конструкцию, а выражение delete вызывает деструктор и освобождает память.

2 голосов
/ 23 августа 2011

Означает ли это, что память дурака освобождается (т. Е. A, b, c недопустимы) или это означает только то, что происходит в освобождении функции ~ Fool () (т. Е. Массив удаляется только?)

Fool::~Fool() не знает, хранится ли экземпляр Fool в динамическом хранилище (через new) или хранится ли он в автоматическом хранилище (т. Е. Объекты стека).Поскольку объект перестает существовать после запуска деструктора, вы не можете предполагать, что a, b, c и array останутся действительными после выхода из деструктора.

Однако, посколькуFool::~Fool() ничего не знает о том, как был выделен Fool, вызов деструктора непосредственно для new -распределенного Fool не освободит основную память, которая поддерживает объект.

0 голосов
/ 23 августа 2011

Самый простой способ увидеть, что деструктор отличается от освобождения с помощью delete, это когда выделение происходит в первую очередь автоматически:

{
  Fool fool;
  // ~Fool called on exit from block; nary a sign of new or delete
}

Обратите внимание, что контейнеры STL полностью используют явный вызов деструктора. Например, std::vector<> обрабатывает хранилище и время жизни содержащихся объектов совершенно отдельно.

0 голосов
/ 23 августа 2011

Вы должны не обращаться к a, b и c после вызова деструктора, даже если это явный вызов деструктора.Вы никогда не знаете, что ваш компилятор помещает в ваш деструктор, который может сделать эти значения недействительными.

Однако память фактически не освобождается в случае явного вызова деструктора.Это по замыслу;он позволяет очистить объект, построенный с использованием размещения new.

Пример:

char buf[sizeof (Fool)];
Fool* fool = new (buf) Fool;  // fool points to buf
// ...
fool->~Fool();
...