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

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

Нужен ли нам виртуальный деструктор, если мои классы не распределяют память динамически?

например.

class A
{
      private: 
      int a;
      int b;

      public:
      A();
      ~A();
};

class B: public A
{     
      private:
      int c;
      int d;

      public:
      B();
      ~B();
};

В этом случае нам нужно пометить деструктор как виртуальный?

4b9b3361

Ответ 1

Проблема заключается не в том, распределены ли ваши классы динамически. Это если пользователь классов выделяет объект B через указатель A, а затем удаляет его:

A * a = new B;
delete a;

В этом случае, если для A нет виртуального деструктора, стандарт С++ говорит, что ваша программа демонстрирует поведение undefined. Это нехорошо.

Это поведение указано в разделе 5.3.5/3 стандарта (здесь ссылается на delete):

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

Ответ 2

Цель виртуального деструктора (т.е. цель создания виртуального деструктора) заключается в том, чтобы облегчить полиморфное удаление объектов посредством удаления-выражения. Если ваш дизайн не требует полиморфного удаления объектов, вам не нужны виртуальные деструкторы. Обратившись к вашему примеру, если вам когда-нибудь понадобится удалить объект типа B с помощью указателя типа A * (полиморфное удаление), вам понадобится виртуальный деструктор, расположенный в иерархии как A. То, как это выглядит с формальной точки зрения.

(Заметьте, BTW, как сказал Нейл, важно то, как вы создаете/удаляете объекты класса, а не как классы управляют своей внутренней памятью.)

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

Последнее обычно выражается как довольно хорошо известное руководство по эффективной практике: если ваш класс имеет хотя бы один виртуальный метод, сделайте деструктор виртуальным. Хотя с формальной точки зрения виртуальный деструктор, возможно, там действительно не нужен, он по-прежнему остается довольно хорошим ориентиром.

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

Ответ 3

Свободная память - это не единственная критическая функция, которую может выполнять деструктор. Его также можно использовать для глобального состояния reset. Не делать этого не будет утечка памяти, но может потенциально вызвать другие проблемы в вашей программе.

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

Ответ 4

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

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

Ответ 5

Цель объявления деструктора как виртуального заключается в возможности вызвать деструктор производного класса всякий раз, когда вы вызываете delete на указателе типа Base, который указывает на объект типа Derived. Это не приведет к поведению undefined.

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

Ответ 6

Если ваша единственная проблема - это память, возможно, вам следует начать с защиты деструктора базового класса (и/или, возможно, других). Тогда, если что-то не скомпилируется, вы поймете, почему. Ref: boost:: any ways.