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

У меня есть этот код:

struct data {
void doNothing() {}
};

int main() {
data* ptr = new data();
ptr->~data();
ptr->doNothing();
::operator delete(ptr);
}

Обратите внимание, что doNothing() вызывается после уничтожения объекта, но до освобождения его памяти. Похоже, что «время жизни объекта» закончилось, однако указатель по-прежнему указывает на правильно распределенную память. Функция-член не имеет доступа к переменным-членам.

Будет ли вызов функции-члена законным в этом случае?

23

Решение

Да, в случае с кодом в ОП. Поскольку деструктор тривиален, его вызов не заканчивает время жизни объекта. [Basic.life] / p1:

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

  • если T тип класса с нетривиальным деструктором (12.4), начинается вызов деструктора, или
  • хранилище, которое занимает объект, используется повторно или освобождается.

/ р5 [class.dtor]:

Деструктор тривиален, если он не предоставлен пользователем и если:

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

Нет, не в общем случае. Вызов нестатической функции-члена после окончания времени жизни объекта — UB. / р5 [basic.life]:

[A] после окончания срока службы объекта и до хранения, которое
занятый объект используется повторно или освобождается, любой указатель на
место хранения, где объект будет или был расположен, может быть
используется, но только ограниченным образом. О строящемся или разрушаемом объекте см. 12.7. В противном случае такой указатель относится к выделенному
хранилище (3.7.4.2) и использование указателя, как если бы указатель имел
тип void*, четко определено. Направление через такой указатель есть
разрешено, но полученное lvalue может использоваться только ограниченным образом,
как описано ниже. Программа не определена
поведение, если:

  • […]
  • указатель используется для доступа к нестатическому члену данных или для вызова нестатической функции-члена объекта, или
  • […]
31

Другие решения

Учитывая [class.dtor]:

Как только деструктор вызывается для объекта, объект более не существует

Этот фрагмент из [basic.life]:

… или после срок службы объекта закончился и до хранения, которое занимает объект
повторно используется или освобожден, любой указатель, который ссылается на место хранения, где объект будет или был расположен
может использоваться, но только ограниченным образом … Программа имеет неопределенное поведение, если:
— …
— указатель используется для доступа к нестатическому члену данных или вызова нестатической функции-члена
объект

оговаривается, что у вас есть неопределенное поведение. Однако здесь есть другой язык — «объект больше не существует» по сравнению с «объект закончился», и ранее в [basic.life] было сказано, что:

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

С одной стороны, у вас нет нетривиального деструктора, поэтому [basic.life] предполагает, что время жизни объекта еще не закончено — хранилище не было повторно использовано или освобождено. С другой стороны, [class.dtor] предполагает, что объект «больше не существует», что, безусловно, звучит так, как будто он должен быть синонимом слова «закончен», но это не так.

Я предполагаю, что ответ «язык-адвокат» таков: это технически не неопределенное поведение и кажется совершенно законным. «Качество кода» ответ: не делайте этого, это в лучшем случае сбивает с толку.

5

Другие ответы верны, но оставим одну деталь:

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

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

Глагол, который опущены другие ответы, который непосредственно предшествует правилу конца жизни, которое они цитировали, говорит

Время жизни объекта является свойством среды выполнения объекта. Говорят, что объект имеет не пустая инициализация если это класс или агрегатный тип, и он или один из его членов инициализируются конструктором, отличным от обычного конструктора по умолчанию. [Примечание: инициализация тривиальным конструктором копирования / перемещения — это не пустая инициализация. — примечание конца] Время жизни объекта типа T начинается, когда:

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

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

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

4
По вопросам рекламы [email protected]