Подтвердить что ты не робот

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

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

struct data {
  void doNothing() {}
};

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

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

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

4b9b3361

Ответ 1

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

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

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

[class.dtor]/р5:

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

В ситуации OP исходный объект все еще живет, поэтому это предостережение не применяется.