Предположим, я реализую некоторый класс 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();
}
Это кажется крайне неудобным и чревато ошибками... Так действительно ли этот код действителен?
Что вы подразумеваете под "крайне неудобно"? Это тот же объем кода, что и (недопустимое) присваивание. И да, это допустимо — если только конструктор
A
не выбросит исключение, и в этом случае вам конец.Ваш деструктор должен вызывать функцию
clear()
, которая выполняет "очистку", а не наоборот. Кроме того, если пользователь действительно и действительно хотел установить свой объект на совершенно новый, чистый лист, то позвольте ему явно сделать это, т.е.1. Что произойдет, если конструктор выбросит? 2. Нельзя просто
*this = A();
?Я почти уверен, что это незаконно в текущем стандарте С++. Однако я слышал, что в самом первом черновике языка C++ (1983 г.) было разрешено присваивать
this
, и поэтому такой тип взлома был возможен.@PaulMcKenzie, да, это справедливое замечание по поводу «очистки». Кроме того, на самом деле мне нужно реализовать данный интерфейс с методом
clear()
, поэтому я не могу принимать решения о том, как позволить пользователю взаимодействовать с этим классом. Это означает, что строкаnew (this) A();
должна остаться...@HolyBlackCat, вы должны опубликовать свой (2.) в качестве ответа.
@DrewDormann Я этого не делал, потому что не уверен, что тип подвижен.
@prapin А? Назначение OP
*this
, а неthis
, что всегда было законным (или было бы законным, если бы они не вызывали деструктор).@HolyBlackCat это справедливо, но я бы заметил, что попытка
*this = A();
уже предпринимается. Вы только предлагаете удалить вызов деструктора.связанные/обман: coderhelper.com/questions/1124634/…. в нем не упоминается
this
, но указатель на объект является указателем на объект, поэтому ответ должен быть таким же.Могут возникнуть дополнительные сложности, если
A
является базовым классом, поскольку вы будете уничтожать и воссоздавать базовый объект, а не производную часть всего объекта.Это, к сожалению, законно. В стандарте C++ есть пример, который делает именно это. Но что произойдет, если кто-то создаст класс, производный от этого? Теперь, если деструктор работает нормально (то есть он виртуальный, или им просто не повезло), код преобразовал объект производного типа в объект базового типа.
Не сделает ли вызов
this->~A()
формально недействительными любые внешние указатели или ссылки на этот объектa
, потому что время жизни формально закончилось? AFAIK только результатnew
может ссылаться на вновь созданный объект, а старые не могут. Несмотря на то, что адреса логически будут одинаковыми, мое понимание объектной модели заключается в том, что любые поддерживаемые/удерживаемые ссылки будут признаны недействительными, и все старые указатели необходимо будет восстановить или изменитьstd::launder
.