Допустимо ли вызывать деструктор для «этого», а затем присваивать «этому» новое значение?

avatar
DL33
1 июля 2021 в 17:10
137
1
2

Предположим, я реализую некоторый класс A с методом clear(), который должен устанавливать состояние объекта в "совершенно новое" состояние, как если бы он был только что создан с помощью конструктора:

  • Я должен освободить все ресурсы, которые использует текущий объект (точно то же самое делает A::~A()),
  • и затем я должен снова инициализировать эти ресурсы (в точности то же самое делает A::A()).

Итак, моя первоначальная идея была следующей:

void A::clear() {
  this->~A();
  *this = A();
}

Однако мне сказали, что этот код вызывает UB, так как я не могу разыменовать this после вызова его деструктора. Но в то же время мне подсказали и лучшую идею: если мы используем place new, разыменования не будет, так что это на самом деле может сработать:

void A::clear() {
  this->~A();
  new (this) A();
}

Это кажется крайне неудобным и чревато ошибками... Так действительно ли этот код действителен?

Источник
Konrad Rudolph
1 июля 2021 в 17:13
1

Что вы подразумеваете под "крайне неудобно"? Это тот же объем кода, что и (недопустимое) присваивание. И да, это допустимо — если только конструктор A не выбросит исключение, и в этом случае вам конец.

PaulMcKenzie
1 июля 2021 в 17:18
9

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

HolyBlackCat
1 июля 2021 в 17:22
2

1. Что произойдет, если конструктор выбросит? 2. Нельзя просто *this = A();?

prapin
1 июля 2021 в 17:25
0

Я почти уверен, что это незаконно в текущем стандарте С++. Однако я слышал, что в самом первом черновике языка C++ (1983 г.) было разрешено присваивать this, и поэтому такой тип взлома был возможен.

DL33
1 июля 2021 в 17:31
0

@PaulMcKenzie, да, это справедливое замечание по поводу «очистки». Кроме того, на самом деле мне нужно реализовать данный интерфейс с методом clear(), поэтому я не могу принимать решения о том, как позволить пользователю взаимодействовать с этим классом. Это означает, что строка new (this) A(); должна остаться...

Drew Dormann
1 июля 2021 в 17:52
0

@HolyBlackCat, вы должны опубликовать свой (2.) в качестве ответа.

HolyBlackCat
1 июля 2021 в 17:57
0

@DrewDormann Я этого не делал, потому что не уверен, что тип подвижен.

HolyBlackCat
1 июля 2021 в 17:57
3

@prapin А? Назначение OP *this, а не this, что всегда было законным (или было бы законным, если бы они не вызывали деструктор).

Drew Dormann
1 июля 2021 в 18:08
0

@HolyBlackCat это справедливо, но я бы заметил, что попытка *this = A(); уже предпринимается. Вы только предлагаете удалить вызов деструктора.

NathanOliver
1 июля 2021 в 18:12
3

связанные/обман: coderhelper.com/questions/1124634/…. в нем не упоминается this, но указатель на объект является указателем на объект, поэтому ответ должен быть таким же.

1201ProgramAlarm
1 июля 2021 в 18:55
0

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

Pete Becker
1 июля 2021 в 20:34
2

Это, к сожалению, законно. В стандарте C++ есть пример, который делает именно это. Но что произойдет, если кто-то создаст класс, производный от этого? Теперь, если деструктор работает нормально (то есть он виртуальный, или им просто не повезло), код преобразовал объект производного типа в объект базового типа.

Human-Compiler
3 июля 2021 в 04:51
0

Не сделает ли вызов this->~A() формально недействительными любые внешние указатели или ссылки на этот объект a, потому что время жизни формально закончилось? AFAIK только результат new может ссылаться на вновь созданный объект, а старые не могут. Несмотря на то, что адреса логически будут одинаковыми, мое понимание объектной модели заключается в том, что любые поддерживаемые/удерживаемые ссылки будут признаны недействительными, и все старые указатели необходимо будет восстановить или изменить std::launder.

Ответы (1)

avatar
Drew Dormann
2 июля 2021 в 21:44
1

действителен ли этот код?

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

Для этого достаточно одного оператора присваивания, если он реализован правильно.

void A::clear() {
  *this = A();
}

Если этот код не работает должным образом, ваш оператор присваивания был неправильно реализован.