Discussion
Team LiB
Previous Section Next Section

Discussion

You rarely need to provide a custom new or delete, but if you need one you usually need both. If you de fine a class-specific T::operator new to do some special allocation, it's very likely you need to define a class-specific T::operator delete as well to do the corresponding special deallocation.

That much may be somewhat basic, but there is a subtler reason for this Item: The compiler might be yearning for an overload of T::operator delete even when you never actually invoke it. That's why you always need to provide operator new and operator delete (and operator new[] and operator delete[]) in pairs.

Say you define a class with customized allocation:



class T {


  // …





  static void* operator new(std::size_t);


  static void* operator new(std::size_t, CustomAllocator&);





  static void operator delete(void*, std::size_t);


};



You establish a simple protocol for allocation and deallocation:

  • Callers can allocate objects of type T with either the default allocator (using new T) or the custom allocator (using new(alloc) T where alloc is an object of type CustomAllocator).

  • The only operator delete that callers can ever invoke is the default operator delete(size_t), so of course you implement such that it correctly deallocates memory allocated either way.

So far, so good.

However, the compiler still needs to covertly call another overload of delete, namely T::operator delete(size_t, CustomAllocator&). This is because the statement



T* p = new(alloc) T;



really expands into something like



// compiler-generated code for T* p = new(alloc) T;


//


void* __compilerTemp = T::operator new(sizeof(T), alloc);


T* p;


try {


  p = new (__compilerTemp) T;      // construct a T at address __compilerTemp


}





catch(...) {                        // constructor failed, attention here…


 T::operator delete(__compilerTemp, sizeof(T), alloc);


 throw;


}



So, quite logically, the compiler automatically inserts code to call the corresponding T::operator delete for the overloaded T::operator new if the allocation succeeds but the constructor fails. The "corresponding" signature is void operator delete(void*, whatever-parameters-new-takes).

Here comes the fly-in-the-ointment part. The C++ Standard (in [C++03] §5.3.4(17)) specifies that the code above will be generated if and only if that overload of operator delete actually exists. Otherwise, the code does not invoke any operator delete at all in the case of a constructor failure. In other words: If the constructor fails, memory will leak. Of six popular compilers tested at the time of this writing, only two issued a warning in such situations.

That's why every overload void* operator new(parms) must be accompanied by its corresponding overload void operator delete(void*, parms)because the compiler wants to call them itself.

    Team LiB
    Previous Section Next Section