Thursday, September 1, 2011

Proxying object allocations with Templates and class level new/delete overrides in C++

When working with languages such as C++, that allow access to the OS memory, you almost certainly run into situations where the standard memory model does not suit your needs. I want to describe a technique here that might come in handy in situations where finer grained control over objects allocations is required.

In my particular situation I needed to create my objects in a static memory area, namely a precreated array. The reason for that was a badly designed server application that relied on dynamic object allocations, which lead to memory fragmentation problems. I couldn't introduce too many changes into the existing code, since that would lead to a lot of retesting for which we didn't have the time nor the resources, hence I opted for a less invasive approach. This is the solution I want to describe in this article.

Class level new and delete overrides

As the title of the article states, I used templates and class level new/delete overrides to accomplish this. Lets take a look at how C++ performs object allocation for a moment. At its core, the C++ runtime uses some variation of malloc and free to request memory from the operating system when new and delete are called, respectively. Since we can override those operators on the class level, we can control the allocation process of an object of a particular type. Knowing that, we can implement a solution that will allow us to intercept memory allocations and manage them ourself.

The static member problem

One key aspect of simply overriding new and delete on a class level is that those methods are implicitly static, hence, if we want to reuse (through inheritance) our memory allocation class in another set of unrelated classes we may end up routing calls to the same memory area, which most likely is not what is needed. I call these the static member problem.

Lets take a look at an example.

The slide above shows a possible scenario, where a base class that implements the necessary memory management routines is inherited by two child classes that are not necessarily related. The problem is that since new and delete are implicitly static any member that they access are required to be static as well. In our case, if the memory manager class uses some member as a memory store that member in turn needs to be static as well, and this will lead to any class that inherits from our memory manager class to use the same member as its store as well - not necessarily what we want.

Templates and the static member problem

As described above, simply overriding new and delete on a class level will at best provide an ad-hoc solution, because of the static member limitation described above. There is however a way of creating a generalized version of this solution - we can use templates to overcome this limitation.

A C++ template is a description of a class, a sort of a proto class. Templates specifies the intents of the class and the algorithms that the class implements without imposing the specific types that those algorithms are going to operate on. Templates are full fledged compile and runtime entities - during compilation time the compiler can detect errors related to incorrect type manipulation because its able to resolve the types when the template class is instantiated. The compiler generates concrete instances for every parameter combination and by the time the linker picks up the code, it already is working with concrete classes just as if you'd defined them manually in your code. They are also runtime entities, because by the time the code is executed, the objects created in memory are of the types generated out of the instantiation of your template with a particular template parameter combination.

In other words, simplifying thing a lot, every time you instantiate a template in your code with a different set of template parameters the compiler generates a concrete class based on that instantiation, hence at run time, every instantiation of an object of a particular template instance is an object of a completely different type.

Using templates and new and delete overrides together

We know that static members are class level entities, meaning that a static member is shared across all the objects of a particular type. Since we generate a different class type for every template parameter combination, we are effectively eliminating the static member problem. We can effectively use templates to create a different instance of the memory manager class for every class that requires to use it. Lets take a look at a possible implementation:

Notice the use of a the CAllocationProxy template in the above graphic, which is used to route calls to the memory manager class.

And here is the code for the CAllocationProxy class:


#include <new>

template < typename TPool, typename TClass >
class CAllocationProxy


 static void
  // Perform required initialization here

 void* operator new ( size_t nSize )
  void* p = NULL;
  // perform required allocation calls here  
  return p;

 void operator delete ( void *p, size_t nSize ) throw()
  if ( !p )
   return; // do nothing on null pointer

  // perform dealocation here

 TPool m_Pool;

template< typename TPool, typename TClass >
TPool CAllocationProxy<TPool,TClass>::m_Pool;

The above class takes two template parameters, TPool and TClass.
  • TPool is our memory allocator - the static member we've been talking about in previous sections. The type passed in itself is not relevant here, however I ended up writing my own memory manager that I would pass as parameter there.
  • TClass is the type of objects that would be allocated by the memory manager. Its not used in the above code, however it could be used to pass the size of the object to be allocated to the memory manager.
The rest is pretty self explanatory.

Lets take a look at how these code can be used:

// declare a class A to use the allocation proxy class
class A : public  CAllocationProxy<MemoryManager, A> {
  // implement class...

// using class A

// initialize proxy instance...

// all allocations are now done by the proxy... 
A myA = new A(); 
// deletion is performed by the proxy as well...
delete myA; 


I hope that this technique is of use to anyone in the same situation as I was in - where the amount of changes lead to by other solutions were not acceptable. However, I also believe that this is not the only reason to use this particular approach. I believe that it offers more control over the code than other, more traditional approaches - such as placement new or some sort of static create method, which could potentially allow you to achieve a similar effect. Besides that, this will allow you to reuse the code across many different objects and memory managers.

One word of caution, be ware of class level new and delete macro redefines. Many runtimes and frameworks will override new and delete with macros, to intercept those calls in order to allow more control and enhance error reporting/catching - usually this is only done in debug builds. To fix it use something like the following code:

// undef any new macros
#ifdef new
#define __new new
#undef new

// undef any delete macros
#ifdef delete
#define __delete delete
#undef delete
// rest of code here
// restore new macros
#ifdef __new
#define new __new
#undef __new

// restore delete macros
#ifdef __delete
#define delete __delete
#undef __delete

Thanks for reading!

No comments: