How C++ placement new works?

It’s really, really simple: new can be thought of as doing two things:

  1. Allocating the memory.
  2. Placement-constructing the object in the allocated memory.

There’s no guarantee that malloc is actually used by the implementation, but typically it is. You cannot assume it about the implementation, but for the purpose of understanding it’s an OK assumption.

Thus, the following are thought of as being equivalent:

auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓ 
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");

Same goes for delete:

delete obj1;
// ↑ can be thought of as equivalent to ↓ 
obj2->~string();
free(obj2);

You can then easily reason about it all when you see new and delete for what they really are: an allocation followed by constructor call, and a destructor call followed by deallocation.

When you use placement new, you’ve decided to take care of the first step separately. The memory has to be still allocated somehow, you just get to have full control over how it happens and where does the memory come from.

You thus must keep track of two things, separately:

  1. The lifetime of the memory.

  2. The lifetime of the object.

The code below demonstrates how these are independent of each other:

#include <cstdlib>
#include <string>
#include <new>

using std::string;

int main() {
    auto obj = (string*)malloc(sizeof(string));  // memory is allocated
    new(obj) string("1");  // string("1") is constructed
    obj->~string ();       // string("1") is destructed
    new(obj) string("2");  // string("2") is constructed
    obj->~string ();       // string("2") is destructed
    free(obj);             // memory is deallocated
}

Your program has UB if the lifetime of the object extends past the lifetime of memory. Make sure that the memory always outlives the life of the object. For example, this has UB:

void ub() {
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory!

But this is OK:

void ub() {
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
    buf->~string();                           // string("1") is destructed
}                                             // memory is deallocated

Note how you need to properly align the automatic buffer using alignas. The lack of alignas for an arbitrary type results in UB. It might appear to work, but that’s only to mislead you.

There are some specific types where not calling the destructor and not aligning the memory properly does not lead to UB, but you should never assume such things about a type. Call your destructors and do the alignment, it won’t cost you anything if it turns out to be unnecessary – no extra code would be generated for such a type.

struct S {
  char str[10];
}

Leave a Comment