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:
- We perform a
memsetto demonstrate what happens if the memory returned bymallochappens 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:
- 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
ptrto benullptrif and only if it doesn’t point to valid memory. In the move assignment, we need to know that whenptr != nullptrit’s safe to free the memory pointed to byptr. - 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). - Same as before, we must call the dtor to free the memory allocated by
array; and then release the memory for thearrayobject 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.