Я знаю, что явный вызов деструктора может привести к неопределенному поведению из-за двойного вызова деструктора, как здесь:
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
return 0; // Oops, destructor will be called again on return, double-free.
}
Но что, если мы назовем размещение новым, чтобы «воскресить» объект?
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
new (&foo) std::vector<int>(5);
return 0;
}
Более формально:
new
) и затем, прежде чем этот объект будет разрушен, вызовите размещение нового на нем, чтобы «восстановить» его?Пример использования (хотя этот вопрос больше о любопытстве): я хочу «переназначить» объект, который не имеет operator=
,
я видел этот вопрос, который говорит, что «переопределение» объекта, который имеет нестатический const
участники незаконны. Итак, давайте ограничимся объемом этого вопроса для объектов, которые не имеют каких-либо const
члены.
Первый, [basic.life]/8
четко говорится, что любые указатели или ссылки на оригинал foo
будет ссылаться на новый объект, который вы строите в foo
в твоем случае. Кроме того, имя foo
будет ссылаться на новый объект, построенный там (также [basic.life]/8
).
Во-вторых, вы должен убедитесь, что существует объект оригинального типа, для которого используется хранилище foo
прежде чем покинуть его сферу; так что, если что-то выбрасывает, вы должны поймать это и прекратить свою программу ([basic.life]/9
).
В целом, эта идея часто заманчива, но почти всегда ужасна.
(8) Если по истечении времени жизни объекта и до повторного использования или освобождения хранилища, в котором занятый объект, создается новый объект в месте хранения, которое занимал исходный объект, указатель, указывающий на исходный объект, ссылка, которая ссылается на исходный объект, или имя исходного объекта будет автоматически ссылаться на новый объект и после запуска времени жизни нового объекта может использоваться для манипулирования новым объектом, если:
- (8.1) хранилище для нового объекта точно перекрывает место хранения, которое занимал исходный объект, и
- (8.2) новый объект того же типа, что и исходный объект (без учета cv-квалификаторов верхнего уровня), и
- (8.3) тип исходного объекта не является константно-квалифицированным и, если тип класса, не содержит нестатических
элемент данных, тип которого является константным или ссылочным типом, и- (8.4) исходный объект был наиболее производным объектом (1.8) типа
Т и новый объект является наиболее производным
объект типа T (то есть они не являются подобъектами базового класса).(9) Если программа заканчивает время жизни объекта типа T со статическим (3.7.1), потоком (3.7.2) или автоматическим (3.7.3) сроком хранения и если T имеет нетривиальный деструктор, Программа должна гарантировать, что объект
оригинальный тип занимает то же место хранения, когда происходит неявный вызов деструктора; в противном случае поведение программы не определено. Это верно даже в том случае, если блок завершается с исключением.
Есть причины вручную запускать деструкторы и делать новые. Что-то простое, как operator=
не один из них, если вы не пишете свой собственный вариант / любой / вектор или подобный тип.
Если вы действительно хотите переназначить объект, найдите std::optional
реализация и создание / уничтожение объектов с использованием этого; это осторожно, и вы почти наверняка не будете достаточно осторожны.
Это не очень хорошая идея, потому что вы все равно можете запустить деструктор дважды, если конструктор нового объекта выдает исключение. То есть деструктор всегда будет запускаться в конце области, даже если вы покидаете область исключительно.
Вот пример программы, которая демонстрирует это поведение (Идеальная ссылка):
#include <iostream>
#include <stdexcept>
using namespace std;
struct Foo
{
Foo(bool should_throw) {
if(should_throw)
throw std::logic_error("Constructor failed");
cout << "Constructed at " << this << endl;
}
~Foo() {
cout << "Destroyed at " << this << endl;
}
};
void double_free_anyway()
{
Foo f(false);
f.~Foo();
// This constructor will throw, so the object is not considered constructed.
new (&f) Foo(true);
// The compiler re-destroys the old value at the end of the scope.
}
int main() {
try {
double_free_anyway();
} catch(std::logic_error& e) {
cout << "Error: " << e.what();
}
}
Это печатает:
Построен в 0x7fff41ebf03f
Уничтожено в 0x7fff41ebf03f
Уничтожено в 0x7fff41ebf03f
Ошибка: конструктор не удался