Делает ли уничтожение и воссоздание объекта недействительными все указатели на этот объект?

Это продолжение этого вопроса. Предположим, у меня есть такой код:

class Class {
    public virtual method()
    {
        this->~Class();
        new( this ) Class();
    }
};

Class* object = new Class();
object->method();
delete object;

что является упрощенной версией того, что предлагает этот ответ.

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

Делает ли это снова действительным указатель на вызываемый объект?


person sharptooth    schedule 20.09.2012    source источник
comment
Разве аннулирование указателей не уничтожит полностью цель размещения new?   -  person Luc Touraille    schedule 20.09.2012
comment
Почему эта функция виртуальная? Это немного страшно.   -  person Kerrek SB    schedule 20.09.2012
comment
Кроме того, если конструктор Class вызывает исключение, вы сталкиваетесь с этической дилеммой. (И пожалуйста не называйте свой класс Class ...)   -  person Kerrek SB    schedule 20.09.2012
comment
@Luc Touraille: Ну, я не знаю нормальных случаев, когда новое размещение вызывается для воссоздания объекта в том же месте.   -  person sharptooth    schedule 20.09.2012
comment
Обеспокоены ли вы, в частности, тем фактом, что замена выполняется из метода объекта, более того виртуального метода, или мы можем заменить ваш пример кода более простым, например, этим: struct T {}; T * obj = new T; obj->~T(); new( obj ) T;?   -  person Luc Touraille    schedule 20.09.2012
comment
@Luc Touraille: Основная проблема заключается в том, что рассматриваемый указатель не имеет обратной связи с объектом, поэтому он должен быть функцией-членом, свободной функцией или другой переменной-указателем, хранящей тот же адрес.   -  person sharptooth    schedule 20.09.2012
comment
@sharptoots, стандарт также охватывает это :) Such use of explicit placement and destruction of objects can be necessary to cope with dedicated hardware resources and for writing memory management facilities.   -  person Zdeslav Vojkovic    schedule 20.09.2012


Ответы (6)


Это явно одобрено в 3.8: 7:

3.8 Время жизни объекта [basic.life]

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

Приведенный пример:

struct C {
  int i;
  void f();
  const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
  if ( this != &other ) {
    this->~C(); // lifetime of *this ends
    new (this) C(other); // new object of type C created
    f(); // well-defined
  }
  return *this;
}
person ecatmur    schedule 20.09.2012
comment
Возможно, наиболее обременительным из различных требований является то, что объект не должен иметь никаких const или ссылочных элементов данных (как и его члены, поскольку, применяя этот трюк к объекту, вы применяете тот же трюк ко всем его членам и их члены и т. д.). Такие элементы данных, вероятно, являются основной причиной, по которой кто-то может подумать, что я не могу мутировать объект, поэтому мне нужно уничтожить и воссоздать его, но они ошибаются. - person Steve Jessop; 20.09.2012
comment
Так заманчиво проголосовать против небезопасного в отношении исключений оператора присваивания с самопроверкой ... но потом я прочитал, что это был пример из Стандарта. -1 к Стандарту. - person Puppy; 24.09.2012

Собственно, это нормально. Однако без крайней осторожности он превратится в отвратительную часть UB. Например, любые производные классы, вызывающие этот метод, не получат воссозданного правильного типа - или что произойдет, если Class() вызовет исключение. Кроме того, на самом деле это ничего не дает.

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

person Puppy    schedule 20.09.2012
comment
Ваши замечания о потенциальном UB ценны, но на самом деле вы не приводите никаких аргументов, подтверждающих ваше утверждение, что это нормально. Не могли бы вы уточнить? - person Luc Touraille; 20.09.2012
comment
Есть стандартная цитата, прямо описывающая эту практику как законную. В частности, указатели не являются недействительными до тех пор, пока существует объект правильного типа в нужном месте, и то, как он туда попал, не является их проблемой, так сказать. - person Puppy; 24.09.2012

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

Этот точный сценарий описан в разделе 3.8.7 стандарта C ++:

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

Тем не менее, это интересно только как обучающий код, как производственный код, это ужасно :)

person Zdeslav Vojkovic    schedule 20.09.2012
comment
Строго говоря, вызов деструктора не очищает внутреннее состояние, он фактически уничтожает объект (согласно стандартной терминологии (§12.4)). Однако ваше замечание о том, что указатели становятся недействительными только при освобождении памяти, кажется верным (см. §3.7.4.2). Даже если в памяти, на которую указывает указатель, больше нет объекта, память все равно выделяется, поэтому указатель все еще действителен (по крайней мере, это моя интерпретация стандарта). - person Luc Touraille; 20.09.2012
comment
Вы правы, но я в основном описывал то, что на самом деле происходит под капотом - я знаю немало людей, которые думают, что освобождение памяти каким-то образом связано с методом деструктора. Я уточнил ответ. - person Zdeslav Vojkovic; 20.09.2012

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

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

person ypnos    schedule 20.09.2012
comment
Что, черт возьми, realloc имеет отношение к чему-либо? - person Puppy; 20.09.2012
comment
Ничего, просто напомнил, что иногда адреса меняются. И это, очевидно, единственная причина, по которой указатель может стать недействительным. В этом сценарии нет. - person ypnos; 20.09.2012

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

person Community    schedule 20.09.2012

Создание нового объекта на месте разрушенного не делает снова действительными указатели. Они могут указывать на действительный новый объект, но не на объект, на который вы изначально ссылались.

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

Это было бы особенно сложно отладить.

person Suncat2000    schedule 20.09.2012
comment
Тоже неверно. Указатель должен указывать только на некоторый действительный объект (правильного типа) во время разыменования, чтобы быть действительным. Указатели и ссылки, которые были действительны до method, действительны после method. - person Puppy; 20.09.2012
comment
@DeadMG: Ваш комментарий неверен. Объект может иметь неправильный тип, что делает использование существующего указателя непредсказуемым. Указатель можно разыменовать, но это не делает его действительным. Если новый объект имеет правильный тип, это сбивает с толку, а поведение программы ненадежным. Оценка –1 за мой ответ неуместна. - person Suncat2000; 21.09.2012
comment
Это полностью востребовано. Если он реконструирует правильный тип, поведение будет четко определено. Тот факт, что отлаживать трудно, безусловно, верно, но неверно, что указатели на старый объект недействительны. - person Puppy; 24.09.2012
comment
Мой ответ правильный. Это верно, что указатели на старый объект недействительны , потому что старого объекта нет! Хотя указатель, безусловно, может быть разыменован , если он указывает на правильно типизированный объект new, результат операции, которая зависит от этих данных, не будет правильным и, следовательно, < i> недопустимый. Вам может не понравиться мой ответ, но с уважением, пожалуйста, удалите свой голос против. - person Suncat2000; 25.09.2012