Безопасно ли называть размещение новым для `this` для тривиального объекта?

20

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

Допустим, у меня есть тривиальный класс, который не владеет никакими ресурсами и имеет пустой деструктор и конструктор по умолчанию. У него есть несколько переменных-членов с инициализацией в классе; ни один из них не является const.

Я хочу повторно инициализировать и объект такого класса без написания deInitметода от руки. Насколько это безопасно?

void A::deInit()
{
  new (this)A{};
}

Я не вижу никаких проблем с ним - объект всегда находится в допустимом состоянии, thisвсе еще указывает на тот же адрес; но это C ++, поэтому я хочу быть уверен.

Amomum
источник
2
Есть ли какие-либо члены объекта const?
Натан Оливер
2
Если это действительно, будет ли это эквивалентно *this = A{};?
Кевин
2
@Amomum *this = A{};означает, this->operator=(A{});то есть создать временный объект и назначить его *this, заменив значения всех элементов данных значениями временного объекта . Так как это то, что вы хотите, и (на мой взгляд) более читабельно, чем новое место размещения, я бы пошел с этим вместо этого.
Кевин
1
@ Кевин, боже мой, ты прав. Чем - я думаю, что это должно быть равным, если копия исключена?
Amomum
1
вместо того, чтобы объяснять класс словами, гораздо лучше написать полный класс, а не только один метод. см. sscce.org
BЈовић

Ответы:

17

Так же, как и законность delete this, размещение новых для thisтакже разрешено, насколько я знаю. Кроме того, в отношении того this, могут ли впоследствии использоваться другие уже существующие указатели / ссылки, существует несколько ограничений:

[Basic.life]

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

  • хранилище для нового объекта точно перекрывает место хранения, которое занимал исходный объект, и
  • новый объект того же типа, что и исходный объект (без учета cv-квалификаторов верхнего уровня), и
  • тип исходного объекта не является константно-квалифицированным, и, если тип класса, не содержит какого-либо нестатического члена данных, тип которого является константно-квалифицированным или ссылочным типом, и
  • ни исходный объект, ни новый объект не являются потенциально перекрывающимися подобъектами ([intro.object]).

Первые два удовлетворены в этом примере, но последние два должны быть приняты во внимание.

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

static_assert(std::is_trivial_v<A> && std::is_copy_assignable_v<A>);

Конечно, поскольку присваиваемость является требованием, вместо этого вы могли бы просто использовать тот, *this = {};который я ожидал бы создать ту же программу. Возможно, более интересный вариант использования мог бы заключаться в повторном использовании памяти *thisдля объекта другого типа (что не соответствовало бы требованиям для использования this, по крайней мере, без повторной интерпретации + отмывания).

Подобно тому delete this, что новое размещение thisвряд ли можно назвать «безопасным».

eerorika
источник
Интересно. Можно ли убедиться, что все эти условия удовлетворены static_assert на некоторых типах черт? Не уверен, есть ли кто-нибудь о постоянных членах ...
Amomum
1
@ Amomum Я не думаю, что быть подобъектом - это то, что можно проверить. Константные или ссылочные члены сделали бы класс не присваиваемым, что, я не думаю, могло бы случиться иначе для тривиального класса.
eerorika
Означает ли это, что строгий псевдоним входит в игру относительно константности? Можете ли вы привести пример, где это может войти в игру?
darune
1
@darune Создайте const-объект, поместите на него новое место размещения, используйте оригинальное имя объекта (или существующий указатель или ссылку), и поведение будет неопределенным. Практически то же самое, что и строгая оптимизация псевдонимов, если не совсем то же самое.
eerorika
1
Инверсия delete ptrесть new T(). Инверсия new(ptr)T{}есть ptr->~T();. stackoverflow.com/a/8918942/845092
Mooing Duck
7

Правила, которые покрывают это, находятся в [basic.life] / 5

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

и [basic.life] / 8

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

  • хранилище для нового объекта точно перекрывает место хранения, которое занимал исходный объект, и

  • новый объект того же типа, что и исходный объект (без учета cv-квалификаторов верхнего уровня), и

  • тип исходного объекта не является константно-квалифицированным, и, если тип класса, не содержит какого-либо нестатического члена данных, тип которого является константно-квалифицированным или ссылочным типом, и

  • ни исходный объект, ни новый объект не являются потенциально перекрывающимися подобъектами ([intro.object]).

Поскольку ваш объект тривиален, вам не нужно беспокоиться о [basic.life] / 5, и пока вы удовлетворяете пунктам из [basic.life] / 8, тогда он безопасен.

NathanOliver
источник