Когда не следует использовать виртуальные деструкторы? - PullRequest
92 голосов
/ 19 ноября 2008

Есть ли когда-нибудь веская причина, чтобы не объявлять виртуальный деструктор для класса? Когда вам следует избегать написания одного?

Ответы [ 12 ]

69 голосов
/ 19 ноября 2008

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

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

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

66 голосов
/ 19 ноября 2008

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

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
}
25 голосов
/ 19 ноября 2008

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

6 голосов
/ 19 ноября 2008

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

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

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

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

5 голосов
/ 21 ноября 2008

Не все классы 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. Если вы передаете указатели на объекты этого класса, не говоря уже о его подклассах, то вы делаете это неправильно.

5 голосов
/ 19 ноября 2008

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

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

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

4 голосов
/ 17 февраля 2016

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

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

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

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

3 голосов
/ 13 апреля 2010

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

1 голос
/ 12 мая 2011

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

Это редкий случай, но это случается.

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

1 голос
/ 19 ноября 2008

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

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

...