I want to implement my own heap allocation function to do the same as the C++ new operator when it comes to allocating elements of a class type. My allocation function (for Windows) does not use the new operator or the standard library, but only HeapAlloc. So my problem is when allocating elements of a class type :
// Allocation function definition.
template <typename Type>
Type* Alloc(int Count) {
return reinterpret_cast<Type*>(HeapAlloc(GetProcessHeap(), 0, sizeof(Type) * Count));
}
// Allocation function use.
Alloc<Class>(5);
The function allocates 5 Class
, which is a simple class with a single default constructor. The constructor does not get called, and I can see why. The only thing I do is allocate some memory and reinterpret_cast
it.
Where I get confused though, is with the new operator :
new Class[5];
The default constructor gets called! The new operator is in the end (on Windows of course) also calling HeapAlloc
. The function does some other stuff too, but none of it seems to really have an impact on the final result.
How can new
call the constructors of each element it allocates, and can I do the same? Or is this just some built-in feature (even though it's part of the standard library)?
So called new
expressions (new int[5]
) call operator new
(similar to malloc
) and the objects' constructor. You can use placement new
expressions or std::construct_at
from C++20 to call the constructors manually. Your code could end up looking like this:
// Raw memory allocation function
void* AllocBytes(size_t size, size_t align) {
void* p = HeapAlloc(GetProcessHeap(), 0, align);
assert(reinterpret_cast<uintptr_t>(p) % align == 0 &&
"Invalid alignment");
return p;
}
// Raw memory deallocation function
void DeallocBytes(void* p, size_t size, size_t align) {
// Use Windows API to deallocate memory
}
// Allocation AND construction function
template <typename Type>
Type* Alloc(size_t Count) {
// Allocate memory for 'Count' elements
Type* array = reinterpret_cast<Type*>(AllocBytes(Count * sizeof(Type), alignof(Type)));
// Construct 'Count' elements in the memory buffer
for (size_t i = 0; i < count; ++i) {
std::construct_at(&array[i]); // From <memory> in C++20
// _Or_ a placement new expression
::new (&array[i]) Type();
}
return array;
}
// Destruction AND deallocation function
template <typename Type>
void Dealloc(Type* p, size_t Count) {
// Destroy 'Count' elements in the memory buffer
for (size_t i = 0; i < count; ++i) {
std::destroy_at(&array[i]); // From <memory> in C++17
// _Or_ explicit destructor calls
array[i].~Type();
}
DeallocBytes(p, Count * sizeof(Type), alignof(Type));
}
Then you can use the functions like this:
// Allocate 5 elements
Class* p = Alloc<Class>(5);
// Deallocate 5 elements
Dealloc(p, 5);
Note that using std::construct_at
/std::destroy_at
or placement new expressions/manual destructor calls doesn't really make a difference. std::construct_at
and std::destroy_at
are constexpr
and more explicit or perhaps easier to understand, but you can do it either way.