views:

381

answers:

4

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* operator new( size_t size, Allocator* a )
{
    return a->Alloc( size );
}

template<class T> inline void MyDelete( T* p, Allocator* a )
{
    if( p )
    {
        p->~T();
        a->Free( p );
    }
}

The C++ language specifies that, for placement delete, you have to explicitly call the ~dtor. The compiler doesn't do it for you. Whether this is a templatised operator delete or explicit function as shown.

See http://www2.research.att.com/~bs/bs_faq2.html#placement-delete

The problem is - how can I get this to work for array delete[]? I know I need to iterate through the array and call ~dtor myself. Therefore I need the size of the array,

Edited for clarity

I can store this information or infer it from the block size. However, the problem is the compiler (MSVC v9) does different things if I am allocating an array of objects with destructors compared to ones without, i.e. if there is a dtor it will allocate an extra 4 bytes. This is because the compiler for standard delete[] needs to do the same thing and can pair up the appropriate code for delete[].

However in my own "placement" delete[] I have no way of knowing what the compiler did or determining safely at compile time if the class has a dtor.

E.g.

char buf[ 1000 ];

MyClass* pA = new( buf ) MyClass[ 5 ];

Here the value of pA is buf + 4 if there exists ~MyClass() and the amount of memory allocated is sizeof(MyClass) * 5 + 4. However if there is no dtor then pA == buf and the amount of memory allocated is sizeof(MyClass) * 5.

So my question is - is this behaviour a language standard and consistent across compilers or is it a peculiar to MSVC? Has anyone else got a good solution to this problem? I guess the only option is to not use new[] and do the construction myself which is fine but then the calling code syntax is a little unusual .. or force every class to have a destructor.

A: 

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.

Jeff Paquette
+4  A: 

When in doubt go to the expert:

http://www2.research.att.com/~bs/bs%5Ffaq2.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.

chollida
Yes I read the link - it's the same as in the question no? It provides no solutions for arrays, the problem is there is no way to deduce whether the compiler has allocated extra space or not so I don't know where my the actual array begins. This could be a MSVC specific thing as well ...
Gwaredd
@Gwaredd, I was trying to get you to have a closer look at the link, where it says that there is no such thing as placement delete and if you want to do what your trying to do, you'll need to track the size yourself. :)
chollida
Yeah, I didn't make the problem very clear :o The issue is the compiler does two different things for new[] depending on the presence of a dtor. Just wondering if anyone had a solution :)
Gwaredd
+1  A: 

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.

Charles Salvia
I didn't explain myself very clearly in the question. I know I need to manually call the destructors and store whatever information I need to make that possible. However, the compiler generates the initialise code for me. In the case of array new[] the compiler (MSVC 9) generates different code if the class has a dtor or not. For normal delete[] I presume the compiler generates the appropriate destruct code, however for placement delete it doesn't (as per C++ spec) and in my delete[] I have no way of knowing what the compiler did in the general case ... unless I'm missing something?
Gwaredd
+2  A: 

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.)

Matt Gordon
Gwaredd
Gwaredd