when globally overloading operators new and delete, which versions need to be implemented to cover all cases?
it seems before c++17 it was enough to implement void* operator new(size_t bytes)
and void operator delete(void* ptr)
since the array versions and the non-throwing versions of the default implementation were implemented using these two functions.
But what about c++17? for example https://en.cppreference.com/w/cpp/memory/new/operator_new doesn't mention that (4) would be implemented by calling (3) (wherease (2) is mentioned to be implemented by calling (1)).
So, in c++17, what's the minimum of operators that need to have all calls to versions of new and delete (other than classes with local overrides) be covered by the replacements?
From cppreference https://en.cppreference.com/w/cpp/memory/new/operator_new#Global_replacements (And a similar note with operator delete
):
The standard library implementations of the nothrow versions (5-8) directly calls the corresponding throwing versions (1-4). The standard library implementation of the throwing array versions (2,4) directly calls the corresponding single-object version (1,3). Thus, replacing the throwing single object allocation functions is sufficient to handle all allocations.
So you need to replace these functions:
void* operator new(std::size_t count);
void* operator new(std::size_t count, std::align_val_t al);
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t al) noexcept;
And the rest will be implemented in terms of those four.
If you don't have an aligned malloc-equivalent to replace the aligned versions with, here's a simple implementation of the aligned new
/delete
in terms of the non-aligned versions:
#include <cstddef>
#include <new>
#include <memory>
void* operator new(std::size_t count, std::align_val_t al) {
std::size_t align = static_cast<std::size_t>(al);
if (align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) [[unlikely]] return operator new(count);
std::size_t actually_allocating = align + count;
if (actually_allocating < count || (actually_allocating += sizeof(void*)) < (align + count)) [[unlikely]] {
// overflow
throw std::bad_alloc();
}
void* unaligned = operator new(actually_allocating);
void* aligned = unaligned;
std::align(align, 0, aligned, actually_allocating);
// Store a pointer to the start of the aligned memory, to be retrieved by delete
::new (static_cast<void*>(static_cast<char*>(aligned) - sizeof(void*))) void*(unaligned);
return aligned;
}
void operator delete(void* ptr, std::align_val_t al) noexcept {
if (static_cast<std::size_t>(al) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) [[likely]] {
ptr = *static_cast<void**>(static_cast<void*>(static_cast<char*>(ptr) - sizeof(void*)));
}
operator delete(ptr);
}