I'm trying to implement a custom allocator for C++ that works on any form of new/delete/malloc/free.
How my program works, I allocate at the start of the program a memory pool of x bytes and work with them. For example, when someone writes int* a= new int;
my program will return the address from the memory pool which is available and marks it as allocated and that address along with the size allocated is removed from the memory pool. When someone writes delete a;
the address is returned to the memory pool and can be used again.
My problem is that I don't fully understand how new(placement)
works and how should I deal with it, because when my function gets called to allocate memory on new/malloc I have as a parameter only the size of the memory the program needs and I just return an available address to that memory to be used. Consider the following example
auto p = (std::string*)malloc(5 * sizeof(std::string));
void * placement = p;
new(placement) std::string(4, (char)('a'));
std::cout<< *p;
On the first line my custom allocated will return to p an address from my memory pool where there is memory available of a total of 5* sizeof(std::string))
, on the third line my custom allocator will allocate again memory returning another address. When I print *p
it prints exactly what I was expected aaaa
.
Is this how it should work?
A normal new
does two things:
allocate storage; and
construct an object.
Now we want to separate these two steps. Allocating raw storage is easy, but there is no "native" way to construct an object at a given address in C++. Therefore, the new
operator is overloaded to serve this purpose, by returning the given pointer for the first step.
We don't need a corresponding delete
, because we can call the destructor manually. In C++17, std::destroy_at
was added to the standard library. Since C++20, std::construct_at
can be used to construct an object instead of placement new:
std::construct_at(p, 4, 'a');
The C++ Super-FAQ explains placement new very well:
There are many uses of placement new. The simplest use is to place an object at a particular location in memory. This is done by supplying the place as a pointer parameter to the new part of a new expression:
#include <new> // Must #include this to use "placement new" #include "Fred.h" // Declaration of class Fred void someCode() { char memory[sizeof(Fred)]; // Line #1 void* place = memory; // Line #2 Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below) // The pointers f and place will be equal // ... }
Line #1 creates an array of
sizeof(Fred)
bytes of memory, which is big enough to hold aFred
object. Line #2 creates a pointerplace
that points to the first byte of this memory (experienced C programmers will note that this step was unnecessary; it’s there only to make the code more obvious). Line #3 essentially just calls the constructorFred::Fred()
. Thethis
pointer in theFred
constructor will be equal toplace
. The returned pointerf
will therefore be equal toplace
.ADVICE: Don’t use this “placement new” syntax unless you have to. Use it only when you really care that an object is placed at a particular location in memory. For example, when your hardware has a memory-mapped I/O timer device, and you want to place a
Clock
object at that memory location.DANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your
Fred
class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.You are also solely responsible for destructing the placed object. This is done by explicitly calling the destructor:
void someCode() { char memory[sizeof(Fred)]; void* p = memory; Fred* f = new(p) Fred(); // ... f->~Fred(); // Explicitly call the destructor for the placed object }
This is about the only time you ever explicitly call a destructor.