Placement new

Placement new #

Placement new refers to running a constructor on allocated memory. Fortunately, this is rarely useful, because we only very rarely allocate memory directly. It’s almost always encapsulated in a container. Even if we do, allocating memory with new T[n] causes the elements to be initialized and there’s no need for placement new. It is however, a very good way to understand how new and delete are very different from malloc and free. We’ll show when the difference matters when using RAII. After all we do occasionally write our own containers and it’s important to know about the pitfalls.

For the (rare) cases where one must use malloc, e.g. because one’s dealing with a C API, it also shows how one can fix a kind of bug by using placement new.

Simple Example #

Let’s start off with a simple class that counts the number of times its dtor was run:

class A {
public:
  A() = default;
  ~A() { dtor_calls += 1; }

  double a = 42.0;
};

Imagine someone implemented a C API we need to use to allocate and deallocate A.

A* allocate() {
  return (A*) malloc(sizeof(A));
}

void deallocate(A* a) {
  free(a);
}

Let’s use it:

A* a = allocate();
std::cout << "after allocate: a.a = " << a->a << "\n";

most likely it’ll print:

after allocate: a.a = 0

This is because malloc will only allocate sizeof(A) bytes, it will not initialize those bytes by running the constructor of A. Note that this is very different from:

A* a = new A();
assert(a.a == 42.0);

Can we fix the issue? Yes, by using placement new:

new(a) A;

Now *a has been correctly initialized; and the address a hasn’t changed, i.e. we’ve initialized A inplace.

Let consider destruction of a. Because free doesn’t run ~A on *a, we have to do it ourselves:

a->~A();
deallocate(a);
assert(dtor_calls == 1);

Array Example #

Let’s consider a more realistic example. An array class. Here’s the very incomplete definition:

class array {
public:
  array() = default;
  array(size_t n) : ptr((double*) malloc(n*sizeof(double))) {}

  ~array() {
    dtor_calls += 1;
    free(ptr);
  }

  const array& operator=(array&& other) {
    free(ptr); // free checks that `ptr` isn't null.

    ptr = other.ptr;
    other.ptr = nullptr;
    return *this;
  }

  double * ptr = nullptr;
};

Note that we chose to use malloc and free to allocate memory for array. Since double is trivial this could be a sensible choice, and we’ll see how it can lead us to make mistakes.

Again, someone provided a C API for allocating the memory:

array* allocate() {
  auto * a = (array*) malloc(sizeof(array));
  memset(a, 255, sizeof(array));  // (1)

  return a;
}

void deallocate(array* a) {
  free(a);
}

One comment about the previous code:

  1. We perform a memset to demonstrate what happens if the memory returned by malloc happens to not be zeroed out.
auto * a = allocate();
// a = array(n);              // (1)
std::cout << "a->ptr = " << a->ptr << "\n";

new(a) array;                 // (2)
*a = array(42);

// Alternatively, we can call a non-default ctor:
// new(a) array(42);

a->~array();                  // (3)
deallocate(a);

A couple of comments:

  1. As tempting as it may be to fix the issue be assigning a properly initialized value, it wont work and cause a segfault. The problem is that we need ptr to be nullptr if and only if it doesn’t point to valid memory. In the move assignment, we need to know that when ptr != nullptr it’s safe to free the memory pointed to by ptr.
  2. The “proper” way of initializing the memory is by using placement new. We can now assign as usual. Note that we could also call a non-default ctor, e.g. new(a) array(42).
  3. Same as before, we must call the dtor to free the memory allocated by array; and then release the memory for the array object itself.

Generalized Array Example #

Let’s naively generalize the previous example as follows:

template<class T>
class array {
public:
  array() = default;
  array(size_t n) : ptr((T*) malloc(n*sizeof(T))) {}
  ~array() {
    free(ptr);
  }

  const array& operator=(array&& other) {
    free(ptr); // free checks that `ptr` isn't null.

    ptr = other.ptr;
    other.ptr = nullptr;
    return *this;
  }

  T* ptr = nullptr;
};

The issue of not initializing memory correctly reappears for array<array<T>>. Which can be fixed by using new in the ctor and delete in the dtor of array.