See my previous two posts on an introduction to placement new if you are unfamiliar with the subject.
- /2007/10/16/c-new-operator-and-placement-new.html
- /2007/10/17/c-placement-new-and-allocators.html
Recently I did a bit of work on the heap management story for our code base. Mainly it was a change to unify the different ways we accessed our internal heap. In the process I unified our operator new story. Part if this involved unifying our placement new allocation overloads. We have several allocators in our code base and I noticed we had several overloads which essentially did the same operation
static inline
void * _cdecl operator new(size_t cbSize, MyCustomAllocator &allocator)
{
return allocator.Alloc(cbSize);
}
static inline
void * _cdecl operator new(size_t cbSize, MyCustomAllocator *allocator)
{
return allocator->Alloc(cbSize);
}
This seemed a bit redundant to me so I deleted the one which contained a pointer overload. A while later I tested my changes and our application almost immediately crashed. It only took a few minutes to discover the problem. We had a lot of operations that followed this pattern.
Student *p = new (pAllocator) Student(); // pAllocator typed to MyCustomAllocator*
As I said in my previous posts, new is just another C++ function. As such it participates in overload resolution. This code now binds to the placement new operator and not the placement allocation overload which takes a reference. At first I thought about using a regex search and replace to solve the issue. However I decided this wasn’t a complete solution. There is no guarantee that my regex will catch every case and any case I miss won’t crash until we actually hit the line of code.
The best case scenario here is to turn that line into a compile error. That would guarantee that I fix every location that is a problem. Since new participates in overload resolution you can solve this with a template.
template <typename T>
static inline
void* _cdecl operator new(size_t cbSize, T* allocator)
{
allocator->ThisWillForceACompileError();
}
This caused all places where a pointer type was passed to new which wasn’t explicitly “void*” to turn into a compiler error. This quickly outlined all of the places I needed to fix up and guaranteed that my fix didn’t allow developers who were accustomed to using a pointer to the allocator to accidentally introduce a bug into the code base (myself included :)).