Когда Ваш деструктор должен быть виртуальным? [дубликат]

По словам Joshua Bloch Эффективный Java (книга, которой нельзя рекомендовать достаточно, и которую я купил благодаря непрерывным упоминаниям на stackoverflow):

значение 31 было выбрано, потому что это - нечетное начало. Если бы это было даже и переполненное умножение, информация была бы потеряна, поскольку умножение 2 эквивалентно смещению. Преимущество использования начала менее ясно, но это традиционно. Хорошее свойство 31 - то, что умножение может быть заменено сдвигом и вычитанием для лучшей производительности: 31 * i == (i << 5) - i. Современные VMs делают этот вид оптимизации автоматически.

(из Главы 3, Объект 9: Всегда переопределяйте хэш-код, когда Вы переопределяете, равняется, страница 48)

42
задан Community 23 May 2017 в 12:09
поделиться

6 ответов

  1. Вам нужен виртуальный деструктор, когда по крайней мере, один из методов класса virtual.

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

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

class A
{
   virtual void f() {}
   ~A() {}
}

class B : public A
{
   void f() {}
   ~B() {}
}

A * thing = new B();
thing->f(); // calls B's f()
delete thing; // calls ~A(), not what you wanted, you wanted ~B()

наличие виртуального ~ A () включает полиморфизм.

virtual ~A() {}

Итак, когда вы сейчас вызываете

delete thing;

, будет вызываться ~ B ().

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

Вы можете видеть, что классы STL не имеют виртуальных деструкторов, поэтому они не должны расширяться (например, std :: vector, std :: string ...). Если вы расширяете std :: vector и вызываете деструктор базового класса через указатель или ссылку, вы определенно не вызовете деструктор своего специализированного класса, что может привести к утечке памяти.

52
ответ дан 26 November 2019 в 23:36
поделиться

Из Часто задаваемые вопросы о стиле и методах C ++ Страуструпа :

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

Много дополнительной информации о , когда ваш деструктор должен быть виртуальным, в C ++ FAQ . (спасибо Стобору)

Что такое виртуальный член? Из FAQ по C ++ :

[20.1] Что такое «виртуальная функция-член»?

С точки зрения объектно-ориентированного подхода это единственная наиболее важная особенность C ++: [6.9], [6.10].

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

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

33
ответ дан 26 November 2019 в 23:36
поделиться

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

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

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

Это просто еще одно практическое правило, но оно удерживает вас от ошибок в дальнейшем.

3
ответ дан 26 November 2019 в 23:36
поделиться

Всегда.

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

0
ответ дан 26 November 2019 в 23:36
поделиться

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

В В общем, методы могут быть определены как виртуальные в базовом классе, это позволит производным классам переопределять виртуальные методы, реализуя их собственную производную конкретную реализацию. Я считаю, что это наиболее наглядно демонстрируется на простом примере. Скажем, у нас есть базовый класс Shape, теперь все производные классы должны иметь возможность рисовать. Объект 'Shape' не знает, как рисовать классы, производные от него, поэтому в 'Shape' class мы определяем виртуальную функцию рисования. т.е. (виртуальная пустота draw ();). Теперь в каждом базовом классе мы можем переопределить эту функцию, реализовав определенный код рисования (т.е. квадрат рисуется иначе, чем круг).

-1
ответ дан 26 November 2019 в 23:36
поделиться

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

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

И, конечно же, Херб Саттер дает обоснование своим утверждениям. Обратите внимание, что он выходит за рамки обычных ответов «когда кто-то удалит объект производного класса с помощью указателя базового класса» и «сделает ваш деструктор виртуальным, если в вашем классе есть какие-либо виртуальные функции».

5
ответ дан 26 November 2019 в 23:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: