Есть ли какие-то конкретные причины для использования не виртуальных деструкторов? - PullRequest
19 голосов
/ 02 января 2012

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

Но почему даже можно объявить такой класс не виртуальным деструктором?Я считаю, что компилятор может решить, когда использовать виртуальные деструкторы.Итак, это упущение в дизайне C ++ или я что-то упустил?

Ответы [ 5 ]

18 голосов
/ 02 января 2012

Есть ли какие-то конкретные причины для использования не виртуальных деструкторов?

Да, есть.

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

В коде, чувствительном к производительности, разница между отсутствием кода и «простым»вызов функции может иметь значение.В отличие от многих языков C ++ не предполагает, что это различие тривиально.

Но почему даже можно объявить такой класс с не виртуальным деструктором?

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

Виртуальный деструктор требуется, когда:

  • вы вызываете delete для указателя
  • для производного объекта через базовый класс

Когда компилятор видит определение класса:

  • , он не может знать, что вы намереваетесь наследовать от этого класса -В конце концов, вы можете получить классы без виртуальных методов
  • , но еще более утомительно: он не может знать, что вы намереваетесь вызывать delete для этого класса

Многие люди предполагают, что полиморфизм требует newing экземпляр, который является просто недостатком воображения:

class Base { public: virtual void foo() const = 0; protected: ~Base() {} };

class Derived: public Base {
  public: virtual void foo() const { std::cout << "Hello, World!\n"; }
};

void print(Base const& b) { b.foo(); }

int main() {
  Derived d;
  print(d);
}

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

В конце концов, это вопрос философии.Там, где это целесообразно, по умолчанию C ++ выбирает производительность и минимальное обслуживание (основным исключением является RTTI).


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

  • -Wnon-virtual-dtor (gcc, Clang): предупреждает всякий раз, когда класс с виртуальной функцией не объявляет виртуальный деструктор, если толькоДеструктор в базовом классе выполнен protected.Это пессимистическое предупреждение, но, по крайней мере, вы ничего не пропустите.

  • -Wdelete-non-virtual-dtor (Clang, перенесен также на gcc ): предупреждает всякий раз, когда deleteвызывается по указателю на класс, который имеет виртуальные функции, но не имеет виртуального деструктора, если класс не помечен как final.Он имеет 0% ложных срабатываний, но предупреждает «поздно» (и, возможно, несколько раз).

3 голосов
/ 02 января 2012

Почему деструкторы не являются виртуальными по умолчанию? http://www2.research.att.com/~bs/bs_faq2.html#virtual-dtor

Рекомендация № 4: Деструктор базового класса должен быть либо общедоступным и виртуальным, либо защищенным и не виртуальным. http://www.gotw.ca/publications/mill18.htm

Смотри также: http://www.erata.net/programming/virtual-destructors/

РЕДАКТИРОВАТЬ: возможно дублирование? Когда не следует использовать виртуальные деструкторы?

2 голосов
/ 02 января 2012

Ваш вопрос в основном таков: "Почему компилятор C ++ не заставляет ваш деструктор быть виртуальным, если в классе есть какие-либо виртуальные члены?"Логика этого вопроса заключается в том, что следует использовать виртуальные деструкторы с классами, которые они намереваются извлечь.

Существует множество причин, по которым компилятор C ++ не пытается перехитритьПрограммист.

  1. C ++ разработан по принципу получения того, за что вы платите.Если вы хотите, чтобы что-то было виртуальным, вы должны попросить об этом.Явное.Каждая функция в виртуальном классе должна быть явно объявлена ​​так (если только она не переопределяет версию базового класса).

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

    Существует ли конкретный допустимый вариант использования для виртуального класса с не виртуальным деструктором?Я не знаю.Может быть, где-то вырожденный случай.Но если вам это нужно по какой-то причине, вы не сможете сказать это по вашему предложению.

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

1 голос
/ 02 января 2012

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

Если методы находятся в DLL, но клиентский код создает экземпляр объекта с прямым new, то клиентский распределитель используется для получения памяти для объекта, но объект заполняется виртуальной таблицей из DLL, которая указывает на деструктор, который использует распределитель, с которым связана DLL, чтобы освободить объект.

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

1 голос
/ 02 января 2012

Похоже, что не виртуальный деструктор имеет смысл, когда класс просто не виртуален (Примечание 1).

Однако я не вижу другого хорошего применения не виртуальных деструкторов.

И я ценю этот вопрос.Очень интересный вопрос!

РЕДАКТИРОВАТЬ:

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

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

Если нам требуется таблица виртуальных функций, И если нам даже потребуется хранилище в куче (как в Java & co.), тогда массив будет просто содержать указатели на фактические элементы «КУДА-ТО» в памяти.

РЕДАКТИРОВАТЬ 2:

Мы можем даже иметьдерево наследования классов вообще без каких-либо виртуальных методов.

Почему?

Потому что, даже если наличие "виртуальных" методов может показаться распространенным и предпочтительным случаем ,это не единственный случай, который мы - человечество - можем себе представить.

Как и во многих деталях этого языка, C ++ предлагает вам выбор.Вы можете выбрать один из предложенных вариантов, обычно вы выбираете тот, который выбирает кто-либо другой.Но иногда вам не нужна эта опция!

В нашем примере класс Vector3 может наследоваться от класса Vector2 и все равно не иметь накладных расходов на вызовы виртуальных функций.Думаю, этот пример не очень хорош;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...