Виртуальный деструктор и поведение undefined

Этот вопрос отличается от "Когда/почему я должен использовать деструктор virtual?".

struct B {
  virtual void foo ();
  ~B() {}  // <--- not virtual
};
struct D : B {
  virtual void foo ();
  ~D() {}
};
B *p = new D;
delete p;  // D::~D() is not called

Вопросы

  • Может ли это быть классифицировано как поведение undefined (мы знаем, что ~D() не будет вызываться наверняка)?
  • Что делать, если ~D() пуст. Это повлияет на код каким-либо образом?
  • При использовании new[]/delete[] с B* p;, ~D(), безусловно, не будет получить вызов, независимо от virtual ness деструктора. Это поведение undefined или четко определенное поведение?

Ответы

Ответ 1

, когда/почему я должен использовать виртуальный деструктор?
Следуйте за Herb Sutters руководство:

Деструктор базового класса должен быть либо открытым, либо виртуальным, либо защищенным и не виртуальным

Можно ли это классифицировать как поведение undefined (мы знаем, что ~ D() не будет вызываться наверняка)?

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

С++ 03 Стандарт: 5.3.5 Удаление

5.3.5/1:

Оператор delete-expression уничтожает наиболее производный объект (1.8) или массив, созданный новым выражением.
удалить-выражение:
:: opt delete cast-expression
:: opt delete [] cast-expression

5.3.5/3:

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

Что делать, если ~D() пусто. Это повлияет на код каким-либо образом?
Тем не менее это undefined Поведение в соответствии со стандартом. Деструктор производного класса, являющийся пустым, может просто заставить вашу программу работать нормально, но это опять-таки реализация определенного аспекта конкретной реализации, технически она по-прежнему является undefined Поведением.

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

Обратите внимание, что он говорит о undefined Поведении.

Стандарт С++ 03: 1.3.12 undefined поведение [defns.undefined]

например, может возникнуть при использовании ошибочной конструкции программы или ошибочных данных, для которых настоящий международный стандарт не налагает никаких требований. undefined поведение также можно ожидать, если в этом международном стандарте отсутствует описание любого явного определения поведения. [ Примечание: допустимое поведение undefined варьируется от полного игнорирования ситуации с непредсказуемыми результатами, поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или выполнения (с выдачей диагностического сообщения). Многие ошибочные программные конструкции не порождают поведение undefined; они должны быть диагностированы.]

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

Ответ 2

  • Undefined Поведение
  • (В первую очередь, эти деконструкторы, как правило, не такие пустые, как вы думаете. Вам все равно придется деконструировать всех ваших участников). Даже если деконструктор действительно пуст (POD?), то это все равно зависит от вашего компилятора. Стандарт undefined. Для всех стандартных забот ваш компьютер может взорвать удаление.
  • Undefined Поведение

На самом деле нет причин для не виртуального публичного деструктора в классе, который должен быть унаследован. Посмотрите эту статью, Руководство № 4.

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

Ответ 3

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

(Конечно, отдельные компиляторы имеют право сделать определенные promises, но я ничего не слышал об этом в этом случае.)

Мне кажется интересным, что в этом случае я думаю, что malloc и free лучше определены в некоторых случаях, чем new и delete. Возможно, мы должны использовать их вместо: -)

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

Base * ptr = (Base*) malloc(sizeof(Derived)); // No virtual methods anywhere
free(ptr); // well-defined

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

Ответ 4

(Я думаю, что могу удалить свой другой ответ.)

Все об этом поведении undefined. Если вам нужно более строго определенное поведение, вы должны изучить shared_ptr или реализовать что-то подобное себе. Следующее определяется поведением, независимо от виртуальности чего-либо:

    shared_ptr<B> p(new D);
    p.reset(); // To release the object (calling delete), as it the last pointer.

Основной трюк shared_ptr - шаблонный конструктор.