Должен ли каждый класс иметь виртуальный деструктор?

Java и C # поддерживают понятие классов, которые нельзя использовать в качестве базовых классов с ключевыми словами final и sealed. В C ++, однако, нет хорошего способа предотвратить создание класса, что ставит автора класса перед дилеммой: должен ли каждый класс иметь виртуальный деструктор или нет?


Изменить: Поскольку C ++ 11 больше не соответствует действительности, вы можете указать, что класс _ 3_.


С одной стороны, предоставление объекту виртуального деструктора означает, что он будет иметь vtable и, следовательно, потреблять 4 (или 8 на 64-битных машинах) дополнительных байта на объект для vptr.

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

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

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


person Motti    schedule 09.12.2008    source источник
comment
Уже есть вопросы о «за» и «против» - это дубликат или это опрос общественного мнения? В последнем случае, может быть, вам стоит создать ответы «да» и «нет» для голосования, а затем закрыть вопрос? Я думаю, что это рекомендуемый способ реализации опроса с несколькими вариантами ответов на SO.   -  person Steve Jessop    schedule 09.12.2008
comment
Дубликаты: stackoverflow.com/questions/270917/, stackoverflow.com/questions/300986/   -  person Steve Jessop    schedule 09.12.2008
comment
и, откровенно говоря, оптимизировать указатель на объект просто смешно. - Это не смешно для мелких предметов. C ++ 0x добавляет контейнер forward_list именно потому, что иногда накладные расходы на один указатель на объект слишком велики - из-за требований к пространству и времени.   -  person Greg Rogers    schedule 09.12.2008
comment
@onebyone, этот вопрос не является дубликатом первого вопроса, который вы указали, который специфичен для классов abstract, и я ссылаюсь на второй в своем вопросе, я не думаю, что это дубликат в том, что вопрос сильно смещен в сторону наличия виртуальных операторов, и я хотел открытого обсуждения.   -  person Motti    schedule 09.12.2008
comment
@Kyralessa, компьютерщик должен делать то, что должен делать компьютерщик :)   -  person Motti    schedule 04.11.2017
comment
Отвечает ли это на ваш вопрос? Почему я должен объявлять виртуальный деструктор абстрактного класса в C ++?   -  person bobobobo    schedule 01.04.2021
comment
@bobobobo, этот вопрос не касался абстрактных классов, поэтому нет, не совсем.   -  person Motti    schedule 04.04.2021


Ответы (7)


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

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

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

person jalf    schedule 09.12.2008
comment
@jalf: если они происходят от него и хотят использовать его полиморфным способом / - person Oszkar; 09.03.2010

Каждый абстрактный класс должен иметь либо,

  • защищенный деструктор, или,
  • виртуальный деструктор.

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

Для абстрактного класса вам уже нужен указатель виртуальной таблицы в объекте, поэтому создание деструктора virtual не имеет (насколько мне известно) больших затрат с точки зрения пространства или производительности во время выполнения. И это имеет то преимущество, что производные классы автоматически имеют свои деструкторы virtual (см. Комментарий @Aconcagua). Конечно, вы также можете сделать деструктор protected virtual для этого случая.

Для неабстрактного класса, не предназначенного для удаления с помощью указателя на него, я не думаю, что есть веская причина иметь виртуальный деструктор. Это приведет к потере ресурсов, но, что более важно, даст пользователям неправильную подсказку. Только подумайте, какой странный смысл было бы давать std::iterator виртуальный деструктор.

person Johannes Schaub - litb    schedule 09.12.2008
comment
Или struct tm из ‹ctime›, который перестанет быть POD и, следовательно, больше не будет совместим с соглашениями о вызовах C. - person Steve Jessop; 09.12.2008
comment
отличный ответ, литб. Я собираюсь изменить весь свой код прямо сейчас. - person e.James; 09.12.2008
comment
Позднее добавление, я знаю, но я не согласен с защищенным деструктором. Кто-то может упустить из виду необходимость снова сделать общедоступный деструктор производного класса виртуальным, а кто-то другой может получить производный деструктор, предполагая уже виртуальный деструктор - и (незаконно) удалить внучатого ребенка через указатель на дочерний ... Конечно, второе лицо тогда возникла ошибка, но ее можно было бы избежать, если бы деструктор уже был виртуальным в самом первом базовом классе, поэтому с точки зрения безопасности я считаю допустимым только второй вариант (конечно, только абстрактные классы). - person Aconcagua; 21.02.2020
comment
@Aconcagua Мое правило для второго автора: каждый неабстрактный инстанцируемый класс должен иметь либо виртуальный публичный деструктор, либо невиртуальный публичный деструктор и объявить final. - person Johannes Schaub - litb; 23.02.2020
comment
Однако ваша идея дать каждому абстрактному классу виртуальный деструктор звучит интересно. Может мне стоит принять это. Мой пример с std::iterator несколько плох, потому что std::iterator совсем не абстрактный. Я поправлю. - person Johannes Schaub - litb; 23.02.2020

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

person Nemanja Trifunovic    schedule 09.12.2008
comment
Итак, как вы предвидите каждый случай, когда ваш код будет полезен? Возможно, будет какой-то случай, когда ваш класс, который предназначен для использования вами неполиморфно, будет полезен только как базовый класс в каком-то очень конкретном экземпляре. - person v010dya; 27.06.2016

Проверьте эту статью от Herb Sutter:

Рекомендация №4: деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и невиртуальным.

person Paolo Tedesco    schedule 10.12.2008
comment
Обратите внимание, что он говорит о классах, которые должны быть базовыми классами, этот вопрос конкретно касается классов, не предназначенных для использования в качестве базовых классов. - person Motti; 10.12.2008

Я бы ответил «нет» на общий вопрос. Не каждому классу он нужен. Если вы знаете, что класс никогда не должен быть унаследован от него, тогда нет необходимости нести незначительные накладные расходы. Но если есть шанс, будьте осторожны и положите его туда.

person Evan Teran    schedule 09.12.2008

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

person shurli pataka    schedule 12.05.2010

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

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

person Patrick Hogan    schedule 09.12.2008