I know that calling destructor explicitly can lead to undefined behavior because of double destructor calling, like here:
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
return 0; // Oops, destructor will be called again on return, double-free.
}
But, what if we call placement new to "resurrect" the object?
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
new (&foo) std::vector<int>(5);
return 0;
}
More formally:
new
) and then, before this object is destructed, call placement new on it to "restore" it?Example usecase (though this question is more about curiosity): I want to "re-assign" an object which does not have operator=
.
I've seen this question which says that "overriding" object which has non-static const
members is illegal. So, let's limit scope of this question to objects which do not have any const
members.
First, [basic.life]/8
clearly states that any pointers or references to the original foo
shall refer to the new object you construct at foo
in your case. In addition, the name foo
will refer to the new object constructed there (also [basic.life]/8
).
Second, you must ensure that there is an object of the original type the storage used for foo
before exiting its scope; so if anything throws, you must catch it and terminate your program ([basic.life]/9
).
Overall, this idea is often tempting, but almost always a horrible idea.
(8) If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
- (8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and
- (8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
- (8.3) the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
- (8.4) the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
(9) If a program ends the lifetime of an object of type T with static (3.7.1), thread (3.7.2), or automatic (3.7.3) storage duration and if T has a non-trivial destructor, the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined. This is true even if the block is exited with an exception.
There are reasons to manually run destructors and do placement new. Something as simple as operator=
is not one of them, unless you are writing your own variant/any/vector or similar type.
If you really, really want to reassign an object, find a std::optional
implementation, and create/destroy objects using that; it is careful, and you almost certainly won't be careful enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With