operator delete after both operator new and placement new?

102 Views Asked by At

Suppose I want to put an object on the "heap", but I need to have some additional memory allocated after it.

My understanding is that the following would be a standard-compliant way of doing this:

(Assume for the moment that nothing is overriding new, and the destructor of SomeClass doesn't throw)

#include <new>

void f(std::size_t n) {
    void* buf = ::operator new(sizeof(SomeClass) + n);
    SomeClass* obj = new(buf) SomeClass;

    // Do stuff with obj

    obj->~SomeClass();
    ::operator delete(buf);
}

But let's say I don't want to keep the buf variable around the entire time. Maybe I create the object in one function and then destroy it in some other funnction later, and it's inconvenient for me to keep passing around buf along with obj. Would it be valid to do something like this:

#include <new>

void f(std::size_t n) {
    void* buf = ::operator new(sizeof(SomeClass) + n);
    SomeClass* obj = new(buf) SomeClass;

    // Do stuff with obj

    obj->~SomeClass();
    ::operator delete(static_cast<void*>(obj));
}

I wasn't able to figure out conclusively from the reference whether the pointer returned by placement new, if converted to void*, is considered "the same" pointer returned by operator new.

Finally, is there any chance that something like this is valid:

#include <new>

void f(std::size_t n) {
    void* buf = ::operator new(sizeof(SomeClass) + n);
    SomeClass* obj = new(buf) SomeClass;

    // Do stuff with obj

    delete obj;
}

I assume that it's not valid, but technically obj was returned by a new expression, and it's in memory allocated by a new operator, so maybe?

EDIT: It just occured to me, would another way to do this be by adding an override for the operator new and then using it in a placement new? In other words, is something like this valid:

#include <new>

enum class ExtraSpace : std::size_t {}; 

void* operator new(std::size_t size, ExtraSpace n) {
    return ::operator new(size + static_cast<std::size_t>(n));
}

void f(std::size_t n) {
    SomeClass* obj = new(ExtraSpace(n)) SomeClass;

    // Do stuff with obj

    delete obj;
}

(I'm working with C++14, but would also be interested to hear of any differences for other standards)

1

There are 1 best solutions below

2
Artyer On
    void* buf = ::operator new(sizeof(SomeClass) + n);
    SomeClass* obj = new(buf) SomeClass;

    delete obj;

might be undefined behaviour in C++14 (Wording of [expr.delete]p10 modified by the resolution of CWG1788):

If deallocation function lookup finds both a usual deallocation function with only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter, the function to be called is selected as follows:

  • [For array delete[], ...]
  • Otherwise, it is unspecified which of the two deallocation functions is selected.

If the unsized operator delete((void*) obj) is selected, then you are fine. But if the sized operator delete((void*) obj, sizeof(SomeClass)) is selected, then you have undefined behaviour because the size is not the same size that was passed to operator new.

GCC by default calls the sized operator delete(void*, std::size_t), and Clang by default calls the unsized operator delete(void*).


As for the void* buf variable, void* operator new(std::size_t, void* ptr) is specifically defined to return ptr unchanged ([new.delete.placement]p2). So obj and buf have the same value (point to the same address in C++17), and so operator delete(static_cast<void*>(obj)) is equivalent to operator delete(buf).