Вызывает ли деструктор явное уничтожение объекта полностью? - PullRequest
14 голосов
/ 24 июня 2009

Если я вызываю деструктор явным образом (myObject. ~ Object ()), гарантирует ли это, что объект будет надлежащим образом уничтожен (вызывая все дочерние деструкторы)?

Хорошо, некоторый код:

class Object
{
   virtual ~Object()
   {}
};

class Widget : public Object
{
   virtual ~Widget()
   {}
};

...
Object* aWidget = new Widget(); //allocate and construct
aWidget->~Object(); //destroy and DON'T deallocate

Я знаю, что могу просто удалить объект, но я не хочу. Я хочу сохранить выделенную память под рукой в ​​качестве важной оптимизации.

Спасибо!

Ответы [ 11 ]

16 голосов
/ 24 июня 2009

Ответ ... почти всегда.

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

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

ОДНАКО вторая общая библиотека не была перекомпилирована. Это означает, что он не знал о недавно добавленном определении виртуального объекта. Delete вызывается из второй общей библиотеки, просто называемой free, и не вызывает виртуальную цепочку деструкторов. Результатом стала неприятная утечка памяти.

10 голосов
/ 24 июня 2009

Да. Но святой дым, вы уверены в этом? Если это так, я бы использовал размещение new для создания вашего Widget. Использование размещения new и затем явный вызов деструктора - приемлемая, хотя и необычная идиома.

Редактировать : рассмотрите возможность выделения памяти вручную, вместо использования new для выделения первого объекта, а затем повторно используйте его память впоследствии. Это позволяет вам полностью контролировать память; Например, вы можете выделять большие порции за раз, вместо того, чтобы выделять отдельный блок памяти для каждого Widget. Это было бы неплохой экономией, если бы память действительно была таким дефицитным ресурсом.

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

6 голосов
/ 24 июня 2009

Да, он вызовет все дочерние деструкторы, поэтому он будет работать так, как вы ожидаете.

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

Поэтому, если вы используете этот подход, будьте осторожны с этим:

#include <iostream>

class A
{
public: 
    A(){};
    ~A()
    {
        std::cout << "OMG" << std::endl;
    }
};

int main()
{
    A* a = new A;
    a->~A();
    delete a;
    return 0;
}

output:
OMG
OMG 

Деструктор вызывается второй раз, когда на объекте фактически вызывается delete, поэтому, если вы удаляете указатели в своем деструкторе, убедитесь, что вы установили их в 0, так что во время вызова деструктора ничего не произойдет (поскольку удаление нулевого указателя ничего не делает).

6 голосов
/ 24 июня 2009

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

Как вы, похоже, понимаете, это редкое действие, но, возможно, в качестве части хорошо протестированной и документированной библиотеки это может быть полезно. Но задокументируйте (и запишите) это, поскольку, хотя он действителен и безопасен, каждый сопровождающий (включая вас) никогда не будет чувствовать себя комфортно с ним.

5 голосов
/ 24 июня 2009

Пожалуйста, избавьте себя от реальных головных болей и используйте Boost Object Pool , который звучит как существующая реализация вашего паттерна источник / сток. Он выделит большие куски памяти, разделит их на правильный размер для вашего объекта и вернет их вам (после вызова конструктора). Когда вы удаляете объекты, им вызывается деструктор, и они помещаются в связанный список объектов для повторного использования. Он будет автоматически увеличиваться и уменьшаться и обеспечивать, чтобы экземпляры ваших объектов имели тенденцию быть близко друг к другу в памяти.

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

3 голосов
/ 24 июня 2009

Да. Деструктор вызывает любые члены-деструкторы в порядке LIFO, затем деструкторы базового класса, и нет никакого способа предотвратить его вызов от этих деструкторов *. Стек объектов гарантированно раскручивается.

Инициализация и финализация отделены от выделения и освобождения памяти в C ++ именно так, чтобы при возникновении особого случая имелся однозначный синтаксис, в котором программист приложения может выразить свое намерение компилятору.

Edit:

  • Я полагаю, что, вызывая abort () или longjmp (), можно фактически запретить запуск деструкторов членов и базового класса.
2 голосов
/ 24 июня 2009
Контейнеры

STL делают это. Фактически, распределитель STL должен предоставлять метод destroy, который вызывает деструктор объекта (allcators также предоставляет метод deallocate для освобождения памяти, которая использовалась для хранения объекта). Однако совет от Страуструпа ( Язык программирования C ++ 10.4.11) -

Обратите внимание, что явных вызовов деструкторов ... следует избегать везде, где это возможно. Иногда они необходимы. ... Новичок должен подумать трижды, прежде чем явно вызывать деструктор, а также попросить более опытного коллегу, прежде чем сделать это.

2 голосов
/ 24 июня 2009

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

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

См. Здесь для небольшой информации:

http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9
1 голос
/ 25 июня 2009

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

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

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

0 голосов
/ 24 июня 2009

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

...