Когда не следует использовать виртуальные деструкторы?

Есть ли веская причина не объявлять виртуальный деструктор для класса? В каких случаях лучше не писать?


person Mag Roader    schedule 19.11.2008    source источник


Ответы (12)


Нет необходимости использовать виртуальный деструктор, если верно любое из следующих утверждений:

  • Нет намерения выводить из него классы
  • Нет экземпляра в куче
  • Нет намерения хранить указатель суперкласса

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

person sep    schedule 19.11.2008
comment
прекрасный обзор. я отказался от своего ответа в пользу этого. +1 :) - person Johannes Schaub - litb; 19.11.2008
comment
Это плохой ответ. Нет необходимости отличаться от не должно, и никакое намерение не отличается от сделанного невозможным. - person Windows programmer; 19.11.2008
comment
Также добавьте: нет намерения удалять экземпляр через указатель базового класса. - person Adam Rosenfield; 19.11.2008
comment
Это не совсем ответ на вопрос. Где у вас есть веская причина не использовать виртуальный дтор? - person mxcl; 19.11.2008
comment
Я считаю, что когда нет необходимости что-то делать, это хороший повод не делать этого. Это следование принципу простого дизайна XP. - person sep; 20.11.2008
comment
Говоря, что у вас нет намерения, вы делаете огромное предположение о том, как будет использоваться ваш класс. Мне кажется, что в большинстве случаев самым простым решением (которое, следовательно, должно быть по умолчанию) должно быть наличие виртуальных деструкторов, и избегать их только в том случае, если у вас есть конкретная причина не делать этого. Так что мне все еще любопытно, что могло бы быть хорошей причиной. - person ckarras; 03.06.2010
comment
Это не относится точно к куче, а больше к области видимости лексической переменной. Значение любой локальной переменной, когда она является объектом класса, хорошо известно во время компиляции, поэтому не может быть путаницы в том, какой деструктор вызывать. Значения локальных переменных - это именно те значения, которые хранятся в стеке, а не в куче; поэтому только объекты, размещенные в куче, должны быть осторожны с тем, какой деструктор будет вызываться. - person Luke Maurer; 24.10.2012
comment
@Windowsprogrammer: Это правда! Многие ли из нас совершили грубую ошибку, наследовав от std :: vector? Почти всегда это ужасная идея, но идеально инкапсулирует, не должно или невозможно / сделать невозможным. - person kevinarpe; 16.08.2016
comment
Пожалуйста, поправьте меня, если я ошибаюсь, но сказано, что когда применяется любое условие, но: Нет экземпляра в куче, у вас может быть подкласс < / i> с динамической памятью, и следующий код приведет к утечке ресурсов: Base *b = new Derived(); delete b; см. - person Frank Sebastià; 03.01.2018
comment
Выделение @ FrankSebastià с помощью new - это размещение в куче. - person Andy Borrell; 03.09.2019
comment
@ FrankSebastià Я понимаю, что вы имеете в виду, и следует пояснить второй момент: нет создания экземпляров в куче производных классов. - person Hans Olsson; 20.11.2020

Чтобы ответить на вопрос явно, т.е. когда вам не объявлять виртуальный деструктор.

C ++ '98 / '03

Добавление виртуального деструктора может изменить ваш класс с POD (простые старые данные) * или с агрегированного на не -POD. Это может помешать компиляции вашего проекта, если ваш тип класса где-то агрегатно инициализирован.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

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

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[* Тип POD - это тип, который имеет определенные гарантии в отношении структуры памяти. Стандарт действительно только говорит, что если вы скопируете из объекта с типом POD в массив символов (или беззнаковых символов) и обратно, то результат будет таким же, как у исходного объекта.]

Современный C ++

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

Для случая многоточия это больше не неопределенное поведение, теперь оно условно поддерживается семантикой, определяемой реализацией (N3937 - ~ C ++ '14 - 5.2.2 / 7):

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

Объявление деструктора, отличного от =default, будет означать, что это нетривиально (12.4 / 5)

... Деструктор тривиален, если он не предоставлен пользователем ...

Другие изменения в Modern C ++ уменьшают влияние проблемы агрегированной инициализации, поскольку может быть добавлен конструктор:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}
person Richard Corden    schedule 19.11.2008
comment
Вы правы, и я ошибался, производительность - не единственная причина. Но это показывает, что в остальном я был прав: программисту класса лучше включить код, чтобы предотвратить наследование класса кем-либо еще. - person Windows programmer; 20.11.2008
comment
дорогой Ричард, не могли бы вы прокомментировать еще немного то, что вы написали. Я не понимаю вашу точку зрения, но это, кажется, единственный ценный момент, который я нашел путем поиска в Google) А может быть вы можете дать ссылку на более подробное объяснение? - person John Smith; 23.06.2017
comment
@JohnSmith Я обновил ответ. Надеюсь, это поможет. - person Richard Corden; 29.06.2017

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

person Andy    schedule 19.11.2008
comment
И, действительно, в gcc есть опция предупреждения, которая предупреждает именно об этом случае (виртуальные методы, но не виртуальный dtor). - person CesarB; 19.11.2008
comment
Разве вы не рискуете утечки памяти, если вы производите от класса, независимо от того, есть ли у вас другие виртуальные функции? - person Mag Roader; 20.11.2008
comment
Я согласен с mag. Это использование виртуального деструктора и / или виртуального метода - это отдельные требования. Виртуальный деструктор предоставляет классу возможность выполнять очистку (например, удалять память, закрывать файлы и т. Д.) И также обеспечивает вызов конструкторов всех его членов. - person user48956; 12.05.2011
comment
@MagRoader Теоретически это так, но поскольку проблема возникает только в том случае, если вы сохраняете (и удаляете) указатель на производный объект, выделенный в куче, в указателе для базы, очевидный вопрос: какое использование этого указателя без виртуальных функций? Я вижу только одну возможность: вы ТОЛЬКО используете объект для удаления ресурсов, когда закончите; и в этом случае у вас должен быть виртуальный деструктор без каких-либо других методов. - person Hans Olsson; 26.11.2020

Виртуальный деструктор необходим всякий раз, когда есть вероятность того, что delete может быть вызван для указателя на объект подкласса с типом вашего класса. Это гарантирует, что правильный деструктор вызывается во время выполнения, и компилятору не нужно знать класс объекта в куче во время компиляции. Например, предположим, что B является подклассом A:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

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

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

person Jay Conrod    schedule 19.11.2008
comment
Если ваш код не критичен к производительности, было бы разумно добавить виртуальный деструктор к каждому базовому классу, который вы пишете, просто для безопасности. следует подчеркивать больше в каждом ответе, который я вижу - person csguy; 25.11.2019

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

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

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

person mxcl    schedule 19.11.2008
comment
Просто придирки, но в наши дни указатель часто будет 64-битным, а не 32-битным. - person Head Geek; 03.07.2019

Не все классы C ++ подходят для использования в качестве базового класса с динамическим полиморфизмом.

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

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

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

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

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

person Steve Jessop    schedule 21.11.2008
comment
Полиморфное использование не подразумевает полиморфного удаления. У класса есть множество вариантов использования виртуальных методов, но без виртуального деструктора. Рассмотрим типичное статически определенное диалоговое окно практически в любом наборе инструментов графического интерфейса. Родительское окно уничтожит дочерние объекты, и ему известен точный тип каждого из них, но все дочерние окна также будут использоваться полиморфно в любом количестве мест, таких как проверка нажатия, рисование, API специальных возможностей, которые извлекают текст для текста. голосовые движки и т. д. - person Ben Voigt; 13.04.2010
comment
Верно, но спрашивающий спрашивает, когда следует специально избегать виртуального деструктора. Для описываемого вами диалогового окна виртуальный деструктор бессмыслен, но ИМО не вреден. Я не уверен, что буду уверен, что мне никогда не придется удалять диалоговое окно с помощью указателя базового класса - например, в будущем я могу захотеть, чтобы мое родительское окно создавало свои дочерние объекты с использованием фабрик. Так что вопрос не в том, чтобы избежать виртуального деструктора, просто вы можете не беспокоиться о его наличии. Виртуальный деструктор класса, не подходящего для наследования, вреден, потому что вводит в заблуждение. - person Steve Jessop; 13.04.2010

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

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

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

Другими словами, если в вашем классе есть не виртуальный деструктор, то это очень четкое утверждение: «Не используйте меня для удаления производных объектов!»

person kidfisto    schedule 17.02.2016

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

person Mark Ransom    schedule 12.04.2010

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

Обратите внимание, что поиск в виртуальной таблице выполняется для объекта, если любой метод этого объекта является виртуальным. Так что нет смысла удалять виртуальную спецификацию в деструкторе, если у вас есть другие виртуальные методы в классе.

person Jørn Jensen    schedule 19.11.2008

Если вы абсолютно уверены, что ваш класс не имеет vtable, тогда у вас также не должно быть виртуального деструктора.

Это редкий случай, но бывает.

Наиболее знакомым примером шаблона, который делает это, являются классы DirectX D3DVECTOR и D3DMATRIX. Это методы класса вместо функций для синтаксического сахара, но классы намеренно не имеют vtable, чтобы избежать накладных расходов на функции, потому что эти классы специально используются во внутреннем цикле многих высокопроизводительных приложений.

person Lisa    schedule 11.05.2011

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

Деструктору не обязательно быть виртуальным, если вы не собираетесь наследовать от класса. И даже если вы это сделаете, защищенный невиртуальный деструктор будет столь же хорош, если удаление указателей базового класса не требуется.

person icecrime    schedule 13.11.2010

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

person Windows programmer    schedule 19.11.2008
comment
Полиморфизм, безусловно, замедлит работу. Сравните это с ситуацией, когда нам нужен полиморфизм и мы решили не делать этого, он будет еще медленнее. Пример: мы реализуем всю логику в деструкторе базового класса, используя RTTI и оператор switch для очистки ресурсов. - person sep; 20.11.2008
comment
В C ++ вы не обязаны мешать мне наследовать ваши классы, которые, как вы задокументировали, не подходят для использования в качестве базовых классов. Я обязуюсь использовать наследование с осторожностью. Если, конечно, руководство по домашнему стилю не говорит иначе. - person Steve Jessop; 21.11.2008
comment
... просто создание виртуального деструктора не означает, что класс обязательно будет правильно работать как базовый класс. Так что пометить его как виртуальный только потому, что вместо того, чтобы делать эту оценку, я выписываю чек, который мой код не может обналичить. - person Steve Jessop; 21.11.2008