When is placement new well defined, and what happens to an existing type when calling placement new?

168 views Asked by At

I've seen some examples of placement new, and am a little confused as to what's happening internally with the various types.

A simple example:

using my_type       = std::string;
using buffer_type   = char;

buffer_type  buffer[1000];
my_type*     p{ new (buffer) my_type() };

p->~my_type();

From what I understand, this is valid, though I'm concerned about what happens to the char array of buffer[]. It seems like this is ok to do as long as I don't access the variable buffer in any form after creating a new object in place on its memory.

For the sake of keeping this simple, I'm not concerned about anything to do with proper alignment here, or any other topics of what can go wrong when calling placement new other than: what happens to the original type? Could I use another type such as buffer_type = int to achieve a similar effect (ignoring the possibility of how much memory that will actually take)? Is any POD safe as buffer_type? How about non-POD types? Do I have to tell the compiler that the char array is no longer valid in some way? Are there restrictions on what my_type could be or not be here?

Does this code do what I expect it to, is it well defined, and are there any minor modifications that would either keep this as well defined or break it into undefined behaviour?

2

There are 2 answers

0
Columbo On BEST ANSWER

what happens to the original type?

You mean the original object? It get's destroyed, that is, its lifetime ends. The lifetime of the array object of type buffer_type [1000] ends as soon as you reuse its storage.

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

Note that this implies that we should not use something with a non-trivial destructor for the buffer: The elements' destructors are called at the end of the scope it's defined in. For e.g. std::string as the element type that would imply a destructor call on an non-existing array subobject, which clearly triggers undefined behavior.

If a program ends the lifetime of an object of type T with […] 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.

To avoid that you would have to construct std:strings into that buffer after you're done with it, which really seems nonsensical.

Is any POD safe as buffer_type?

We do not necessarily need PODs - they have a lot of requirements that are not a necessity here. The only things that bother us are the constructor and the destructor.
It matters whether the types destructor is trivial (or for an array, the arrays element types' destructor). Also it's feasible that the constructor is trivial, too.

POD types feel safer since they suffice both requirements and convey the idea of "bare storage" very well.

Are there restrictions on what my_type could be or not be here?

Yes. It shall be an object type. It can be any object type, but it cannot be a reference.

0
tohava On

Any POD type would be safe. Also, while a bit dangerous, I believe most non-POD types whose destructor is empty would work here as well. If the destructor is not empty, it will be called on buffer and try to access its data which is no longer vaild (due to the placement new).