12 ответов:
нет необходимости использовать виртуальный деструктор при наличии любого из указанных ниже условий:
- нет намерения выводить классы из него
- нет экземпляра в куче
- нет намерения хранить в указателе суперкласса
нет конкретной причины, чтобы избежать этого, если вы действительно так нажата на память.
чтобы ответить на вопрос явно, т. е. когда вы должны не объявить виртуальный деструктор.
C++ '98 /' 03
добавление виртуального деструктора может изменить ваш класс отPOD (простые старые данные)* или агрегировать в non-POD. Это может остановить компиляцию вашего проекта, если ваш тип класса где-то инициализирован aggregate.
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)... Деструктор тривиален, если он не предоставляется пользователем ...
другие изменения в современном C++ уменьшают влияние проблемы инициализации aggregate в качестве конструктора могут быть добавлены:
struct A { A(int i, int j); virtual ~A (); int i; int j; }; void foo () { A a = { 0, 1 }; // OK }
Я объявляю виртуальный деструктор тогда и только тогда, когда у меня есть виртуальные методы. Как только у меня есть виртуальные методы, я не доверяю себе, чтобы избежать его создания в куче или хранения указателя на базовый класс. Оба они являются чрезвычайно распространенными операциями и часто будут пропускать ресурсы молча, если деструктор не объявлен виртуальным.
виртуальный деструктор необходим всякий раз, когда есть шанс, что
deleteможет быть призван на указатель на объект подкласса с типом вашего класса. Это гарантирует, что правильный деструктор вызывается во время выполнения без компилятора, который должен знать класс объекта в куче во время компиляции. Например, предположимBявляется наследникомA:A *x = new B; delete x; // ~B() called, even though x has type A*если ваш код не критичен по производительности, было бы разумно добавить виртуальный деструктор каждый базовый класс, который вы пишете, просто для безопасности.
однако, если вы нашли себя
deleteing много объектов в узком цикле, накладные расходы на производительность вызова виртуальной функции (даже тот, который пуст) может быть заметно. Компилятор обычно не может встроить эти вызовы, и процессору может быть трудно предсказать, куда идти. Вряд ли это окажет существенное влияние на производительность, но об этом стоит упомянуть.
виртуальные функции означают, что каждый выделенный объект увеличивает стоимость памяти с помощью указателя таблицы виртуальных функций.
поэтому, если ваша программа предполагает выделение очень большого количества некоторого объекта, было бы целесообразно избегать всех виртуальных функций, чтобы сохранить дополнительные 32 бита на объект.
во всех других случаях вы сэкономите себе отладочные страдания, чтобы сделать dtor виртуальным.
Не все классы 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. Если вы передаете указатели на объекты этого класса, не говоря уже о подклассах, то Вы делаете это неправильно.
хорошая причина не объявлять деструктор виртуальным - это когда это спасает ваш класс от добавления виртуальной таблицы функций, и вы должны избегать этого, когда это возможно.
Я знаю, что многие люди предпочитают просто всегда объявлять деструкторы виртуальными, просто чтобы быть в безопасности. Но если ваш класс не имеет никаких других виртуальных функций, то нет никакого смысла иметь виртуальный деструктор. Даже если вы даете свой класс другим людям, которые затем выводят другие классы из него тогда у них не было бы причин когда - либо вызывать delete на указателе, который был передан вашему классу-и если они это сделают, я бы счел это ошибкой.
хорошо, есть одно единственное исключение, а именно, если ваш класс (mis-)используется для выполнения полиморфного удаления производных объектов, но тогда вы - или другие ребята - надеюсь, знаете, что для этого требуется виртуальный деструктор.
другими словами, если ваш класс имеет невиртуальный деструктор, то это очень четкое заявление: "не используйте меня для удаления производных объектов!"
Если у вас очень маленький класс с огромным количеством экземпляров, накладные расходы указателя vtable могут повлиять на использование памяти вашей программы. Пока у вашего класса нет других виртуальных методов, создание деструктора не виртуальным сохранит эти накладные расходы.
Я обычно объявляю деструктор виртуальным, но если у вас есть критический для производительности код, который используется во внутреннем цикле, вы можете избежать поиска виртуальной таблицы. Это может быть важно в некоторых случаях, как проверка столкновения. Но будьте осторожны с тем, как вы уничтожаете эти объекты, если вы используете наследование, или вы уничтожите только половину объекта.
обратите внимание, что поиск виртуальной таблицы происходит для объекта, если любой метод на этом объекте является виртуальным. Так что нет точка в удалении виртуальной спецификации на деструкторе, если у вас есть другие виртуальные методы в классе.
Если вы абсолютно положительно должны убедиться, что ваш класс не имеет виртуальных методов, то вы не должны иметь виртуальный деструктор, а также.
Это редкий случай, но такое случается.
наиболее знакомым примером шаблона, который делает это, являются классы DirectX D3DVECTOR и D3DMATRIX. Это методы класса вместо функций для синтаксического сахара, но классы намеренно не имеют vtable, чтобы избежать накладных расходов функции, потому что они классы специально используются во внутреннем цикле многих высокопроизводительных приложений.
на операции, которые будут выполняться на базовом классе, и которые должны вести себя виртуально, должны быть виртуальными. Если удаление может быть выполнено полиморфно через интерфейс базового класса, то оно должно вести себя виртуально и быть виртуальным.
деструктор не должен быть виртуальным, если вы не собираетесь наследовать от класса. И даже если вы защищенный невиртуальный деструктор так же хорош, если удаление указателей базового класса не требуется.
ответ на производительность-единственный, который я знаю, у которого есть шанс быть правдой. Если вы измерили и обнаружили, что де-виртуализация ваших деструкторов действительно ускоряет работу, то у вас, вероятно, есть другие вещи в этом классе, которые тоже нуждаются в ускорении, но на данный момент есть более важные соображения. Когда-нибудь кто-то обнаружит, что ваш код предоставит им хороший базовый класс и сэкономит им неделю работы. Вам лучше убедиться, что они делают на этой неделе работайте, копируя и вставляя свой код, а не используя его в качестве основы. Вам лучше убедиться, что вы делаете некоторые из ваших важных методов частными, чтобы никто никогда не мог унаследовать от вас.
Comments