Приводит ли явный вызов деструктора к неопределенному поведению здесь? - PullRequest
5 голосов
/ 20 июля 2010

На мой взгляд, следующий код (из некоторого вопроса C ++) должен привести к UB, но, похоже, это не так.Вот код:

#include <iostream>
using namespace std;
class some{ public: ~some() { cout<<"some's destructor"<<endl; } };
int main() { some s; s.~some(); }

и ответ:

some's destructor
some's destructor

Я узнал из c ++ faq lite, что мы не должны явно вызывать деструктор.Я думаю, что после явного вызова деструктора, объект s должен быть удален.Программа автоматически вызывает деструктор снова, когда он закончил, это должен быть UB.Тем не менее, я попробовал это на g ++, и получил тот же результат, что и ответ выше.

Это потому, что класс слишком прост (без нового / удаления)?Или это вообще не UB в этом случае?

Ответы [ 9 ]

15 голосов
/ 20 июля 2010

Поведение не определено, потому что деструктор вызывается дважды для одного и того же объекта:

  • Один раз, когда вы вызываете его явно
  • Один раз, когда заканчивается область действия и автоматическая переменная уничтожается

Вызов деструктора для объекта, время жизни которого закончилось, приводит к неопределенному поведению в C ++ 03 §12.4 / 6:

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

Время жизни объекта заканчивается, когда его деструктор вызывается в соответствии с §3.8 / 1:

Время жизни объекта типа T заканчивается, когда:

- если T является типом класса с нетривиальным деструктором (12.4), начинается вызов деструктора, или

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

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

Что такое тривиальный деструктор? §12.4 / 3 говорит:

Деструктор тривиален, если он неявно объявлен деструктором и если:

- все прямые базовые классы этого класса имеют тривиальные деструкторы и

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

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

6 голосов
/ 20 июля 2010

Это неопределенное поведение - но, как и в любом UB, одна из возможностей состоит в том, что он (более или менее) работает, по крайней мере, для некоторого определения работы.

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

3 голосов
/ 20 июля 2010

С http://www.devx.com/tips/Tip/12684

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

В вашем случае этоне падает, потому что деструктор не манипулирует никаким полем;на самом деле, в вашем классе вообще нет элементов данных.Если это произойдет, и в теле деструктора вы каким-либо образом манипулируете им, вы, скорее всего, получите исключение во время выполнения при повторном вызове деструктора.

1 голос
/ 20 июля 2010

Скорее всего, он работает нормально, потому что деструктор не ссылается ни на какие переменные члена класса.Если вы попробуете delete переменную внутри деструктора, вы, вероятно, столкнетесь с проблемами, когда она будет автоматически вызываться во второй раз.

Опять же с неопределенным поведением, кто знает?:)

1 голос
/ 20 июля 2010

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

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

class StackPointer<T> {
  T* m_pData;
public:
  StackPointer(T* pData) :m_pData(pData) {}
  ~StackPointer() { 
    delete m_pData; 
    m_pData = NULL; 
  }
  StackPointer& operator=(T* pOther) {
    this->~StackPointer();
    m_pData = pOther;
    return this;
  }
};

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

0 голосов
/ 20 июля 2010

Это неопределенное поведение. Неопределенное поведение - это двойной вызов деструктора, а не самого вызова деструктора. Если вы измените свой пример на:

#include <iostream>
using namespace std;
class some{ public: ~some() { [INSERT ANY CODE HERE] } };
int main() { some s; s.~some(); }

где [ВСТАВИТЬ ЛЮБОЙ КОД ЗДЕСЬ] можно заменить любым произвольным кодом. Результаты имеют непредсказуемые побочные эффекты, поэтому его считают неопределенным.

0 голосов
/ 20 июля 2010

Можете ли вы определить неопределенное поведение, которое вы ожидаете? Undefined не означает случайное (или катастрофическое): поведение данной программы может повторяться между вызовами, это просто означает, что вы не можете полагаться на какое-либо конкретное поведение, потому что оно не определено и нет гарантии того, что произойдет.

0 голосов
/ 20 июля 2010

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

0 голосов
/ 20 июля 2010

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

В любом случае вам никогда не следует вручную вызывать деструктор объекта, за исключением объектов, созданных с помощью размещения-нового.

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