Явный вызов деструктора - программирование
Подтвердить что ты не робот

Явный вызов деструктора

Я наткнулся на следующий фрагмент кода:

#include <iostream>
#include <string>
using namespace std;
class First
{
    string *s;
    public:
    First() { s = new string("Text");}
    ~First() { delete s;}
    void Print(){ cout<<*s;}
};

int main()
{
    First FirstObject;
    FirstObject.Print();
    FirstObject.~First();
}

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

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

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

Является ли текст просто неправильно об ошибке во время выполнения? Или это действительно распространенная ошибка? Или, может быть, мой компилятор реализовал какой-то механизм защиты от подобных вещей?

4b9b3361

Ответ 1

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

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

Так что мне повезло с моим компилятором (VS 2017) или этой конкретной программой.

Я бы сказал, что вам не повезло. Лучшее (для вас, кодера), что может случиться с UB, - это сбой при первом запуске. Если кажется, что он работает нормально, сбой может произойти 19 января 2038 года на производстве.

Является ли текст просто неправильно об ошибке во время выполнения? Или это действительно распространенная ошибка? Или, может быть, мой компилятор реализовал какой-то механизм защиты от подобных вещей?

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

Хорошее чтение о неопределенном поведении: что такое неопределенное поведение?

Ответ 2

Нет, это просто неопределенное поведение из черновика стандарта C++ [class.dtor] p16:

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

и мы можем видеть из определения неопределенного поведения:

поведение, к которому этот документ не предъявляет никаких требований

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

Кроме того, [class.dtor] p15 дает больше контекста в нормативном разделе, который я цитирую выше:

[Примечание: явные вызовы деструкторов нужны редко. Одно из применений таких вызовов - для объектов, размещенных по определенным адресам с использованием нового выражения размещения. Такое использование явного размещения и уничтожения объектов может быть необходимо для управления выделенными аппаратными ресурсами и для записи средств управления памятью. Например,

void* operator new(std::size_t, void* p) { return p; }
struct X {
  X(int);
  ~X();
};
void f(X* p);

void g() {                      // rare, specialized use:
  char* buf = new char[sizeof(X)];
  X* p = new(buf) X(222);       // use buf[] and initialize
  f(p);
  p->X::~X();                   // cleanup
}

- конец примечания]

Ответ 3

Является ли текст просто неправильно об ошибке во время выполнения?

Это неверно.

Или это действительно распространенная ошибка? Или, может быть, мой компилятор реализовал какой-то механизм защиты от подобных вещей?

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

В вашем случае вам (не) повезло *, и это сработало, в то время как для меня это вызвало ошибку (double free).


* Потому что, если вы получили ошибку, вы бы начали отладку, в противном случае, например, в большом проекте, вы могли бы ее пропустить...