Why There Is No Placement Delete Expression in C++

Why there is no placement delete expression in C++?

operator delete is unique in being a non-member or static member function that is dynamically dispatched. A type with a virtual destructor performs the call to its own delete from the most derived destructor.

struct abc {
virtual ~abc() = 0;
};

struct d : abc {
operator delete() { std::cout << "goodbye\n"; }
};

int main() {
abc *p = new d;
delete p;
}

(Run this example.)

For this to work with placement delete, the destructor would have to somehow pass the additional arguments to operator delete.

  • Solution 1: Pass the arguments through the virtual function. This requires a separate virtual destructor for every static member and global operator delete overload with different arguments.
  • Solution 2: Let the virtual destructor return a function pointer to the caller specifying what operator delete should be called. But if the destructor does lookup, this hits the same problem of requiring multiple virtual function definitions as #1. Some kind of abstract overload set would have to be created, which the caller would resolve.

You have a perfectly good point, and it would be a nice addition to the language. Retrofitting it into the existing semantics of delete is probably even possible, in theory. But most of the time we don't use the full functionality of delete and it suffices to use a pseudo-destructor call followed by something like arena.release(p).

Regarding delete expressions the lack of placement delete in C++

If p points to an object of polymorphic class type, you can get the address of the most derived object using dynamic_cast<void*>(p). Thus your customDelete2 can be implemented as follows:

template <class T>
void customDelete2(const T *ptr) {
const void* ptr_to_free = dynamic_cast<const void*>(ptr);
ptr->~T();
std::free(const_cast<void*>(ptr_to_free));
}

(Yes, you can dynamically allocate const objects.)

Since this will only compile for a polymorphic class type, you might want to remove the dynamic_cast to a helper function:

template <class T>
const void* get_complete_object_address(const T* p, std::true_type) {
return dynamic_cast<const void*>(p);
}

template <class T>
const void* get_complete_object_address(const T* p, std::false_type) {
return p;
}

template <class T>
void customDelete2(const T *ptr) {
const void* ptr_to_free = get_complete_object_address(
ptr,
std::integral_constant<bool, std::is_polymorphic<T>::value>{}
);
ptr->~T();
free(const_cast<void*>(ptr_to_free));
}

Why are non-placement `new` and `delete` built into the language and not just regular functions?

If the user's goal was to create an object in some memory location, then new seemed like a natural approach since forwarding references, variadic templates and placement new were not a thing back in those days. As correctly pointed out by @T.C. templates were released in 1990 and placement new in 1989. Variadic templates on the other hand, became a part of C++ only in C++11.

tl;dr There was no way to forward a bunch of arguments to a constructor of an arbitrary type (as you can do these days with make functions).

How does C++ placement delete work internally (C++ runtime)? How to overcome its limitation?

This answer assumes the question refers to user-defined placement allocation functions:

void* operator new  ( std::size_t count, user-defined-args... );
void* operator new[]( std::size_t count, user-defined-args... );

and user-defined placement deallocation functions:

void operator delete  ( void* ptr, args... );
void operator delete[]( void* ptr, args... );

The behaviour of these functions is:

  • operator new: If defined, called by the custom single-object placement new expression with the matching signature. If a class-specific version is defined, it is called in preference to this. If neither is provided by the user, the placement new expression is ill-formed.
  • operator new[]: Ditto but for array form.
  • operator delete: If defined, called by the custom single-object placement new expression with the matching signature if the object's constructor throws an exception. If a class-specific version is defined, it is called in preference to this. If neither is provided by the user, no deallocation function is called.
  • operator delete[]: Ditto but for array form.

To answer your questions:

  1. Don't pay for what you don't use. There might be a setup where no deallocation function is required.

  2. You'll have to include a mechanism whereby the value of the void * pointer being deleted can be used to determine which memory pool it is in. This must be possible because your different pools can't return the same value simultaneously. A naive method might be to have each pool manage a non-overlapping address range and test the pointer against each pool's address range. Or perhaps you could store metadata in memory before the pointed-to location, e.g. the new version would allocate N+16 bytes, store metadata in the first 16, and return to the user a pointer to the 16th byte of the block). Or you could keep a data structure associating each active pointer with metadata.

  3. Evaluation of the new-expression for allocating an object of class type will go something like:

    • Call operator new, passing the arguments
    • Assuming that succeeds, call class constructor (if it's a class type being allocated).
    • If the class constructor throws, and matching operator delete exists, call that function, passing the arguments.
    • Now evaluation of the new-expression is finished, and control returns to either the code that contained the new-expression, or into the exception handling mechanism.

c++ self-defined placement new and placement delete invoking

Placement delete is available only to handle exceptions which occur during evaluation of a placement new expression. If construction finishes successfully, then later on normal delete will be used.

You can call a placement deallocation function explicitly, but it won't have the same behavior as the delete operator (it won't call the destructor automatically).

In your case, the corresponding code would be:

a->~A();
A::operator delete(a, 4);

Yuck!

For arrays it is even worse, because you can't retrieve the number of elements (and number of destructors to call) from the location where the compiler stored that for its own use.

Design your overloaded operator new so that it pairs correctly with one-argument operator delete. Then users of your class can use delete ptr; and std::unique_ptr etc.

If you do require custom deallocation, then an allocation wrapper which returns std::shared_ptr with a custom deleter will be better than custom placement new.

Getting issue with placement new and delete operator

  1. Placement delete is called to free memory when a constructor called from placement new fails. You are not supposed to call any destructors from any version of operator delete, because operator delete frees memory that is left after an object that used to reside there is destroyed (or was never constructed to begin with).
  2. The only way to explicitly call a placement operator delete is to spell out two words operator delete, thus making a function-call-expression. You cannot invoke it from a delete-expression (there is no placement-delete-expression syntax). In your case, you would need to use a qualified name: Object::operator delete. Note that if you remove the explicit destructor call from Object::operator delete, as you should because of the above, the destructor will not be called. There is no way to both invoke the destructor and free the memory in a single call to a placement delete. The easiest way to handle this is to create and use a non-static member function, say void Object::destroy(Mem*).

Placement forms of the operator delete functions

If you don't want to use ::, you don't really have to. In fact, you generally shouldn't (don't want to).

You can provide replacements for ::operator new and ::operator delete (and the array variants, though you should never use them).

You can also, however, overload operator new and operator delete for a class (and yes, again, you can do the array variants, but still shouldn't ever use them).

Using something like void *x = ::operator new(some_size); forces the allocation to go directly to the global operator new instead of using a class specific one (if it exists). Generally, of course, you want to use the class specific one if it exists (and the global one if it doesn't). That's exactly what you get from using void *x = operator new(some_size); (i.e., no scope resolution operator).

As always, you need to ensure that your news and deletes match, so you should only use ::operator delete to delete the memory when/if you used ::operator new to allocate it. Most of the time you shouldn't use :: on either one.

The primary exception to that is when/if you're actually writing an operator new and operator delete for some class. These will typically call ::operator new to get a big chunk of memory, then divvy that up into object-sized pieces. To allocate that big chunk of memory, it typically (always?) has to explicitly specify ::operator new because otherwise it would end up calling itself to allocate it. Obviously, if it specifies ::operator new when it allocates the data, it also needs to specify ::operator delete to match.

placement new and delete

The correct method is:

buf->~Buffer();
::operator delete(mem);

You can only delete with the delete operator what you received from the new operator. If you directly call the operator new function, you must also directly call the operator delete function, and must manually call the destructor as well.

Global placement delete[]

Short answer:

There is no direct support for this usage. If you overload new with a different signature, the compiler considers it an overload of new (not placement new) and adds its own book-keeping code. There is no way (I can find) to say to the compiler "unwind your book-keeping, and call my delete overload matching this signature" - it will only insert code to unwind the book-keeping when calling void operator delete(void* p) or void operator delete[](void* p).

If you do override new with a new signature, the compiler likes you to define a delete with matching signature in case of exceptions during new - this is the only time it gets used.

There is no placement delete in the sense that it is not callable, but it is defined in case of exceptions (to do nothing).

Long answer:

This topic raises some interesting points:

  1. What, exactly, does void* operator new[](size_t sz, Allocator* a) overload?
  2. Is there, or is there not, a "placement delete".
  3. How does one invoke void operator delete[](void* p, Allocator* a), in such a way as the compiler inserts its book-keeping finalization?

Point 1: Much talk about overloading placement new. Given that the compiler is inserting book keeping code, it must be of the opinion that that void* operator new[](size_t sz, Allocator* a) declares an overload of (non-placement) new. It will never insert book-keeping code for placement new, because the point of placement new is you are handling it youself.

Point 2: R.E. "no such thing as placement delete", you will find something that looks awfully like it (and commented as such) in e.g. the VS2k8 new header. It is simply a stub used in cases where an exception occurs during placement new. It does however appear to be true that you cannot invoke placement delete in a meaningful way.

Point 3: If there is a way, I can't find it. This is the heart of the problem.

In terms of a practical solution to the problem, it appears to be a bust.

for example:

//intention: user provides memory pool, compiler works out how many bytes required
//and does its own book-keeping, as it would for a void* operator new[](size_t sz) overload
//calling syntax: CObj* pMyArr = new(pMyMemPool) CObj[20];
void* operator new[](size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }

//problem: don't know the syntax to call this!
//e.g. delete[](pMyMemPool) pMyArr is syntax error
void* operator delete[](void* p, IAlloc* pMemPool)
{ return pMemPool->free(p); }

//nb: can be called as operator delete(pMyArr, pMyMemPool);
//but compiler does not finish its book-keeping or call dtors for you in that case.

Note that this asymmetry exists for non-array new & delete too. However, because (empirically) the compiler in question does no extra book-keeping it can all be made to work. Again, if this is enshrined in standard I don't know.

    void* operator new(size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }

//don't know syntax to get this called by compiler!
void operator delete(void* p, IAlloc* pMemPool)
{ pMemPool->free(p); }

//is ok though, can work around
template<class T> void tdelete(void* p, IAlloc* pMemPool)
{
//no problems, p points straight at object
p->~T();

operator delete(p, pMemPool);
//OR just
pMemPool->free(p);
}

void* operator new[](size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }

//again, don't know syntax to end up here.
void operator delete[](void* p, IAlloc* pMemPool)
{ pMemPool->free(p); }

//can't work around this time!
template<class T> void tarrdelete(void* p, IAlloc* pMemPool)
{
//problem 1: how many to dtor?
for(int i=0; i<???; ++i)
{ reinterpret_cast<T*>(p+i)->~T(); }
//problem 2: p points at first element in array. this is not always the address
//that was allocated originally.
pMemPool->free(?);

//as already explained by OP, no way to tell if p is address allocated or
//address allocated+4 bytes, or something else altogether. this means no way to know what address to un-alloc or how many dtors to call.

}

Finally, I'll state the obvs. - overloads without the extended parameter list do work:

//sz may include extra for book-keeping
void* operator new[](size_t sz)
{ return GAlloc->alloc(sz); }

//works fine, compiler handled book-keeping and p is the pointer you allocated
void operator delete[](void* p)
{ return GAlloc->free(p); }

Summary: Is there syntax that will allow calls to an overload of delete with an extended parameter list, with compiler "magic" enabled. Or, is there a way to add parameters to placement new by override?

Suspected answer: No.

Corollary: You cannot stray from the 6 built-in new signatures with complete freedom. Doing so results in an overload of new, with compiler generated book-keeping, but no access to the corresponding delete to unwind to book-keeping.

Caveat: You can stray from the built-in signatures, but only to inject code you do not need to handle again at delete (e.g. instrumentation). If you fall through to the void* operator new(size_t s) version for the allocation, then delete will still work as normal.

(Some statements of fact are drawn from experiments in the debugger and may only apply to MSVC8 (cl9). OP sits on next desk to me.)



Related Topics



Leave a reply



Submit