Before C++20, you would need to substitute Foo* foo = reinterpret_cast<Foo*>(malloc(sz + sizeof(Foo)));
for Foo* foo = ::new(malloc(sz + sizeof(Foo)) Foo;
. In C++20 and newer your class is an implicit-lifetime class, and as such it doesn't need to be created manually in malloc
ed memory. I wouldn't rely on this C++20 feature, as it's rather obscure.
Don't forget to call the destructor before freeing the memory. Read about std::launder
and consider if you need to use it or not.
This is technically still UB, but only because the standard is defective in its description of reinterpret_cast
. No compiler is going to enforce this.
Also the pointer reachibility rules for std::launder
somewhat imply that it could be UB, but it's not stated explicitly, and again, no contemporary compiler enforces this.
Consider overloading operator new
for your class to have a semblance of a good class allocation interface:
#include <new>
#include <iostream>
struct Foo
{
size_t len = 0;
size_t cap = 0;
char *buf()
{
return reinterpret_cast<char*>(this + 1);
}
void *operator new(std::size_t size, std::size_t cap)
{
return ::operator new(size + cap);
}
// Only if you need custom cleanup in `delete`:
// void operator delete(Foo *ptr, std::destroying_delete_t)
// {
// // Destroy array elements here.
// ptr->~Foo();
// ::operator delete(ptr);
// }
};
Foo *new_foo(size_t sz)
{
Foo* foo = new(sz) Foo;
foo->len = 0;
foo->cap = sz;
return foo;
}
Now new Foo
causes a compilation error, while new(cap) Foo
works and forces you to allocate enough memory for the element. Though ::new Foo
still works, there is no way to disable it.
If you uncomment the destroying operator delete
, you can hook into delete foo
to insert the cleanup for the elements.