c++17: which versions of global operators new/delete must be replaced at least to cover all cases?

Viewed 272

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?

1 Answers

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);
}

Related