I am trying to replace new/delete with my own allocator(s). So, overriding placement new and delete - quite happy with that. Looks something like this ...
void*
There is no such terminology as "placement delete". As you said, if you allocate something with placement new, then when it comes time to deallocate you need to manually invoke the destructor and then also take care of the actual memory buffer allocated for placement new.
But what you're trying to do is not possible without manually keeping track of your own allocation sizes. The reason is that the whole point of "placement new" is to decouple allocation from object initialization. So with placement new, the act of allocating a memory buffer is totally separate from constructing or destructing whatever objects may (or may not) ever find themselves living in that buffer.
So, for example, if you allocate some buffer, like char buf[1000]
, and then you use placement new to construct an array of Foo
objects in that buffer, where is C++ supposed to store the array size information? It's not going to store it in your buffer, because it doesn't know what you want to do with that buffer. So it's up to you to record the size of each allocation, and then properly couple that with deallocation.
When in doubt go to the expert:
http://www.stroustrup.com/bs_faq2.html#placement-delete
But how can we later delete those objects correctly? The reason that there is no built-in "placement delete" to match placement new is that there is no general way of assuring that it would be used correctly. Nothing in the C++ type system allows us to deduce that p1 points to an object allocated in Arena a1. A pointer to any X allocated anywhere can be assigned to p1.
The rest of the link goes onto describe how to remedy the situation.
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:
void* operator new[](size_t sz, Allocator* a)
overload?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.)
You could look the pointer up in your allocator, find out the size from your bookkeeping and compute the number of elements using sizeof T.