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

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


Редактировать: Поскольку C++ 11 больше не соответствует действительности, вы можете указать, что класс - это final.


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

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

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

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

Уже есть вопросы о «за» и «против» - это дубликат или это опрос общественного мнения? В последнем случае, может быть, вам стоит создать для голосования ответы «да» и «нет», а затем закрыть вопрос? Я думаю, что это рекомендуемый способ реализации опроса с несколькими вариантами ответов на SO.

Steve Jessop 09.12.2008 22:01

Дубликаты: stackoverflow.com/questions/270917/…, stackoverflow.com/questions/300986/…

Steve Jessop 09.12.2008 22:04

«и, откровенно говоря, оптимизировать указатель на объект - это просто смешно». Это не смешно для небольших объектов. C++ 0x добавляет контейнер forward_list именно потому, что иногда накладные расходы на один указатель на объект слишком велики - из-за требований к пространству и времени.

Greg Rogers 09.12.2008 22:23

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

Motti 09.12.2008 22:44

@Kyralessa, компьютерщик должен делать то, что должен делать компьютерщик :)

Motti 04.11.2017 21:10

Отвечает ли это на ваш вопрос? Зачем мне объявлять виртуальный деструктор абстрактного класса в C++?

bobobobo 01.04.2021 22:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
51
6
45 388
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

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

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

v010dya 27.06.2016 08:34

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

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

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

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

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

Или struct tm из <ctime>, который перестанет быть POD и, следовательно, больше не будет совместим с соглашениями о вызовах C.

Steve Jessop 09.12.2008 22:12

отличный ответ, литб. Я собираюсь изменить весь свой код прямо сейчас.

e.James 09.12.2008 22:14

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

Aconcagua 21.02.2020 12:42

@Aconcagua Мое правило для второго автора: каждый неабстрактный инстанцируемый класс должен иметь либо виртуальный публичный деструктор, либо невиртуальный публичный деструктор и объявленный final.

Johannes Schaub - litb 23.02.2020 00:09

Однако ваша идея дать каждому абстрактному классу виртуальный деструктор звучит интересно. Может мне стоит принять это. Мой пример с std::iterator несколько неудачен, потому что std::iterator совсем не абстрактный. Я поправлю.

Johannes Schaub - litb 23.02.2020 00:14

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

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

Ответ принят как подходящий

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

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

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

@jalf: если они происходят от него и хотят использовать его полиморфным способом /

Oszkar 09.03.2010 14:26

Проверьте эта статья из Herb Sutter:

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

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

Motti 10.12.2008 22:43

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

Другие вопросы по теме