10

I've been reading about overloading new and delete (and related topics like placement new/delete). One thing that is confusing me thus far is that operator delete's standard signature is (at class-scope):

void operator delete(void *rawMemory, std::size_t size) throw();

Delete is called like this:

MyClass* ptr = new MyClass;
delete ptr;

So, how does delete ptr; provide the second parameter for size? Also, can I assume that the MyClass* is implicitly converted to the void* in this circumstance?

John Humphreys
  • 37,047
  • 37
  • 155
  • 255
  • I'm not sure about the first part, but yes, all pointers can be cast to `void *` implicitly – Shahbaz Oct 19 '11 at 16:00
  • @Shahbaz: true, but note that you *can't* cast back to `MyClass*` (which may be what the OP wants to do). The object has been destroyed by the time `operator delete` is called, so it's invalid to try to access it. – Mike Seymour Oct 19 '11 at 16:05
  • @MikeSeymour, no of course. Like I said, they can be cast to `void *` not the other way around. As the OP asks: _can I assume that the MyClass* is implicitly converted to the void* in this circumstance?_ – Shahbaz Oct 19 '11 at 16:09

3 Answers3

11

Short Answer:

new and delete operators are overloaded at class scope for optimizing allocation for objects of specific class. But there can be special scenarios due to certain beasts like Inheritance which may lead to allocation requests for more than the class size itself,Since the very purpose of new and delete overload is special tuning for objects of size sizeof(Base), nothing larger or smaller, these overloaded operators should forward all other wrong sized memory requests to ::operator new and ::operator delete, to be able to do so, the size parameter needs to be passed as an parameter.

Long Answer:

Consider a Special Scenario:

class Base 
{
    public:
        static void * operator new(std::size_t size) throw(std::bad_alloc);
};



class Derived: public Base 
{
   //Derived doesn't declare operator new
};                                  

int main()
{
    // This calls Base::operator new!
    Derived *p = new Derived;                 

    return 0;
}

In the above sample, because of inheritance The derived class Derived inherits the new operator of the Base class. This makes calling operator new in a base class to allocate memory for an object of a derived class possible. The best way for our operator new to handle this situation is to divert such calls requesting the "wrong" amount of memory to the standard operator new, like this:

void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{

    if (size != sizeof(Base))          // if size is "wrong," i.e != sizeof Base class
    {
         return ::operator new(size);  // let std::new handle this request
    }
    else
    {
         //Our implementation
    }
}

While overloading the delete operator, One must also ensure that since class-specific operator new forwards requests of the "wrong" size to ::operator new, One MUST forward "wrongly sized" deletion requests to ::operator delete, Since the original operators are guaranteed to to handle these requests in a standard compliant manner.

So the custom delete operator will be something like this:

class Base 
{                            
   public:                                 
      //Same as before
      static void * operator new(std::size_t size) throw(std::bad_alloc);       
      //delete declaration
      static void operator delete(void *rawMemory, std::size_t size) throw();      

     void Base::operator delete(void *rawMemory, std::size_t size) throw()
     {
         if (rawMemory == 0) 
         {
              return;                            // No-Op is null pointer
         }

         if (size != sizeof(Base)) 
         {           
             // if size is "wrong,"
             ::operator delete(rawMemory);      //delegate to std::delete
             return;                            
         }
        //If we reach here means we have correct sized pointer for deallocation
        //deallocate the memory pointed to by rawMemory;

        return;
     }
};

Further Reading:
The following C++-Faq entry talks about overloading new and delete in a standard compliant way and might be a good read for you:

How should i write iso c++ standard conformant custom new and delete operators?

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
7

So, how does delete ptr; provide the second parameter for size?

If pointer type is a class type with a virtual destructor, from dynamic information about object type. If it doesn't have a virtual destructor and pointee type matches pointer type - from compile time information about type size. Otherwise delete ptr is undefined behavior.

Tadeusz Kopec for Ukraine
  • 12,283
  • 6
  • 56
  • 83
0

It's the compiler's job to remember the size to release when you call delete. For delete ptr;, it will pass sizeof(MyClass), for delete[] ptr; it has to remember the number of MyClass in the array, and pass the correct size. I have absolutely no idea how this oddball corner of the language came to be. I can't think of any other part of the language where the compiler must remember a run-time value for you.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Presumably so it can be used with POD types without them all having to become classes - as in Java ? – Martin Beckett Oct 19 '11 at 16:19
  • I don't see how POD is related. Basically, `delete[]` is proof that the compiler keeps track of how big dynamic arrays are, but will not tell us that value. We have to use an _additional_ variable in memory to keep track of _our_ copy of the size of the array. – Mooing Duck Oct 19 '11 at 16:28
  • I meant it needs to do magic to add in the sizeof() for PODs but yes it is silly that it keeps track of array size and won't tell you - in the same way that 'c' malloc wouldn't tell you how big an alloc was. It's also pretty silly that since it knows how big an array is that you should need different syntax for an array vs single object. – Martin Beckett Oct 19 '11 at 16:33
  • From what I've seen, some compilers (e.g. MSVC) don't always track array size for free store allocations. When MyClass has a trivial destructor or no destructor, there's no need to keep track of how many destructors to call, so no array header is allocated to store the size. Adding the ability for programmers to query the size of an array allocated from the free store would probably require ABIs to change, plus it would often use more memory than the current behavior. – bk1e Oct 19 '11 at 17:04
  • @bk1e: I would imagine the information is still there somewhere, I can't imagine how a heap would work otherwise. If the info is there, it wouldn't take any more memory. It would definitely require ABIs to change to add it now, but why did they make this decision when making `operator new`? – Mooing Duck Oct 19 '11 at 17:20
  • @Mooing Duck: I have no idea why it was designed this way. `delete[]` doesn't call into the heap to figure out how many destructors to call. If it did, then what would happen when you replace `::operator new` and `::operator delete` with your own heap? Instead, `new[]` stashes the size somewhere for `delete[]` to find, if it thinks `delete[]` will need it. BTW, it also looks like declaring a class-scoped `operator delete[](void*, size_t)` may cause `new[]` to stash the array size even if the class has no destructor. Hooray for implementation details. – bk1e Oct 20 '11 at 07:28