Виртуальный деструктор и неопределенное поведение - PullRequest
15 голосов
/ 22 декабря 2011

Этот вопрос отличается от ' Когда / почему я должен использовать деструктор 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

Вопросы

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

Ответы [ 4 ]

19 голосов
/ 22 декабря 2011

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

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

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

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

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

5.3.5 / 1:

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

5.3.5 / 3:

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

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

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

Обратите внимание, что он стандартно говорит о неопределенном поведении.

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

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

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

7 голосов
/ 22 декабря 2011
  1. Неопределенное поведение
  2. ( Первое замечание: эти деконструкторы, как правило, не так пусты, как вы думаете. Вам все равно придется деконструировать всех своих членов ) Даже еслидеконструктор действительно пустой (POD?), тогда он все еще зависит от вашего компилятора.Это не определено стандартом.При всех стандартных заботах ваш компьютер может взорваться при удалении.
  3. Неопределенное поведение

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

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

2 голосов
/ 22 декабря 2011

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

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

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

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

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

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

0 голосов
/ 22 декабря 2011

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

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

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

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

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