Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Да, есть.

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

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

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

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

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

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

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

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

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

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);
}

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

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


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

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

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

Ответ 2

Почему деструкторы не являются виртуальными по умолчанию? 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/

EDIT: возможно дублировать? Когда вы не должны использовать виртуальные деструкторы?

Ответ 3

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

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

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

  • Если деструктор для класса с виртуальными членами был автоматически создан виртуальным, как бы вы решили сделать его не виртуальным, если это то, что вы так хотели? С++ не имеет возможности явно объявлять метод не виртуальным. Итак, как бы вы переопределили это поведение, основанное на компиляторах.

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

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

Ответ 4

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

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

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

EDIT:

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

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

Если нам нужна таблица виртуальных функций, и если нам даже понадобится память в куче (как в Java и co.), тогда массив будет содержать указатели на фактические элементы "SOMEWHERE" в памяти.

ИЗМЕНИТЬ 2:

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

Почему?

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

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

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

Ответ 5

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

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

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