Monday, February 06, 2006

Cheap Tricks in C++

I came across an interesting bit of C++ code today as I was helping a co-worker troubleshoot a compiler error that he encountered while attempting to upgrade the compiler that we use for our build process. Here is the code (which has been modified drastically for the sake of clarity):

template<class T>
void *AllocateGroup(IAllocator *pAlloc, int count)
{
  void *pData = pAlloc->Allocate(sizeof(T) *count);
  for( int i = 0; i < count; i++)
  {
    new(&((T*)pData)[i]) T();
  }
  return pData;
}

What really confused me was that we were calling 'new' but totally disregarding the result of the memory allocation done by new. It also confused me at first that we were both calling the IAllocator and using new. What was going on here? As far as I could tell we were leaking memory, except that when I examined the disassembly no 'call's were actually being made. I was confused.

T in this particular template instantiation was a MyObject. This is what the MyObject class looked like:

class MyObject
{
public:
  void *operator new(size_t size, void *p)
  {
    return ::operator new(size, p);
  }
}

The operator new had been overloaded to take a void pointer, but as far as I could tell it wasn't doing anything special with it. It was just passing it along to the global operator new. The breakthrough occurred when I realized that the global operator new does not take 2 parameters. That meant that we were overriding operator new somewhere. I did a search across the codebase and indeed found an override of operator new deep within the bowels of our toolkit. Here is what it looked like:

void *operator new(size_t size, void *p)
{
  return p;
}

What the hell? An operator new that does nothing other than return the parameter that was passed into it? And then, all of the sudden, things began to click into place. This operator new was designed specifically to do absolutely nothing. The original code in the template function actually did the memory allocation using the IAllocator and then was looping through each member of the array and forcing the constructor to be called by using this goofy no-op version of operator new.

I want to know who dreamt up this egregious hack. The funny thing is that there is a much simpler way to solve the problem with much less goofiness. If the MyObject class was modified to look like this:

class MyObject
{
public:
  void *operator new[](size_t size, IAllocator *pAlloc)
  {
    return pAlloc->Allocate(size);
  }
};

then the template function could be reduced to this much simpler and easier to understand block of code:

template<class T>
void *AllocateGroup(IAllocator *pAlloc, int count)
{
  return new(pAlloc) T[count];
}

1 comment:

Anonymous said...

umm, ok