Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

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

4.79/5 (12 votes)
23 Jul 2015CPOL5 min read 23.2K  
A technique to use templates and class level new and delete overrides to handle object allocations.

Introduction

When working with languages such as C++ that allow you 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 you need to have more control over object allocations.

In my particular situation, I needed to create my objects in a static memory area, namely a pre-created array. The reason for that was a badly designed server application that relied on dynamic object creation, which led to memory fragmentation problems. I couldn't introduce too many changes into the existing code, since it 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. Let's 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 class. Knowing that, we can implement a solution that will allow us to intercept memory allocations and manage them ourselves.

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.

Let's take a look at an example.

Image 1

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 specification of a class, a sort of a proto class. Templates specify 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 it is 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. We also say that they are 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, 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. Let's take a look at a possible implementation:

Image 2

Notice the use of 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:

C++
#ifndef ALLOCATION_PROXY_H
#define ALLOCATION_PROXY_H

#include <new>

template < typename TPool, typename TClass >
class CAllocationProxy
{
public:
    CAllocationProxy()
    {
    };

    virtual
    ~CAllocationProxy()
    {
    };

    static void
    InitProxyInstance()
    {
        // Perform required initialization here
    };

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

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

        // perform dealocation here
    };

private:
    static 
    TPool    m_Pool;
};

template< typename TPool, typename TClass >
TPool CAllocationProxy<TPool,TClass>::m_Pool;
#endif // ALLOCATION_PROXY_H

The above class takes two template parameters, TPool and TClass.

  • TPool is our memory allocator - the static member we've been talking about in the 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. It is 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.

Let's take a look at how this code can be used:

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

// using class A

// initialize proxy instance...
A::InitProxyInstance();

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

Conclusion

I hope that this technique is of use to anyone in the same situation as I was in - where the amount of changes led 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, beware 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:

C++
// undef any new macros
#ifdef new
#define __new new
#undef new
#endif

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

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

Thanks for reading!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)