Customized Allocators with Operator New and Operator Delete

by Andrei Milea

Why Customize Memory Allocation by Overloading New and Delete?

At times, you will have classes for which you want to specialize memory allocation. Why? You know something about how the class is used. For instance, you might specialize memory allocation for a class in order to squeeze some extra performance out of your program. Suppose you have a linked list and you want to speed up the allocation of new nodes. One way to do this is to maintain a list of deleted nodes, whose memory can be reused when new nodes are allocated. Instead of using the default new and delete, new will be overloaded to try to get a node from the list of deleted nodes; only if no deleted nodes are available would it dynamically allocate memory. Delete will simply add the node to the deleted nodes. This way instead of allocating new memory from the heap (which is pretty time consuming) we will use the already allocated space. The technique is usually called caching.

Another useful reimplementation of new/delete operators is to provide garbage collection for your objects. That is, you can implement a garbage collector similar to the one that Java or C# uses so your objects will be deleted automatically when they are no longer used. (You might wonder why you need to provide your own allocator to write a garbage collector.

One more example usage is creating an arena allocator that makes memory allocations and deallocations lightning fast at the cost of temporarily holding on to more memory than necessary by allocating a large block up front and then carving out one piece at a time.

In order to specialize allocation, you overload operator new and operator delete. (For more on operator overloading, see introduction to operator overloading.) Operator new is used to perform all memory allocation when the new keyword is used, and operator delete is used to deallocate that memory when delete is used. As with the rest of the operators, new and delete can be overloaded to specialize them for a specific class. But first, let's clarify the exact distinction between operator new and the new keyword.

The relationship between Operator New and the New Keyword

Don't be confused by the fact that there is both a new keyword and an operator new. When you write:
MyClass *x = new MyClass;
there are actually two things that happen--memory allocation and object construction; the new keyword is responsible for both. One step in the process is to call operator new in order to allocate memory; the other step is to actually invoke the constructor. Operator new lets you change the memory allocation method, but does not have any responsibility for calling the constructor. That's the job of the new keyword.

As it turns out, it is actually possible to invoke the constructor without calling operator new. That's the job of placement new (covered below).

Changing the default behavior of new and delete

Here's an example of what it would look like to overload new and delete for a particular class.
class Myclass
{
public:
        void* operator new(size_t);
        void operator delete(void*);
};
Both of them are by default static members and do not maintain a this pointer. Overloading can be used for many purposes. For example, we may need to alter the exception thrown in case of failure--std::bad_alloc--and throw something else:
void* Myclass::operator new(size_t size)
{
    void *storage = malloc(size);
    if(NULL == storage) {
            throw "allocation fail : no free memory";
    }
    return storage;
}
Usually we do this in a base class in order to have the functionality of the overloaded new in all derived classes. Implicitly the definition of new is included in every file, but in order to use the size_t declaration and other new related types you must include the header <new> .

The new operator can be implemented using malloc or another C function, called realloc, that handles memory allocation:
void * realloc ( void * ptr, size_t size );
This can be used to change the size of the allocated block at address ptr with the size given in the second parameter. The address pointed to by ptr can actually be changed and the block moved someplace else, in which case the new address will be the returned value. If realloc fails, like malloc, it returns NULL. But realloc will not free the original memory if your memory allocation request fails. Therefore, when you use realloc, be sure to save the previous pointer value in case allocation fails, so that you do not leak memory.

Note that in general, if you overload new, you will likely need to overload delete, and vice versa, because by changing how you allocate memory, you will typically also change how you free memory.

Operator new is invoked implicitly when new is called; there is no syntax for calling operator new explicitly.

Placement New

Standard C++ also supports a second version of new, called placement new, which constructs an object on a preallocated storage. In order for this to work we must provide the address where we want the object to be allocated as a pointer parameter:
(my_class = new (place) Myclass);
So why would you want to use placement new? Placement new is useful for constructing objects in a pre-allocated block of memory. This bypasses the work of operator new by allowing the person constructing the object to choose the memory that it is initialized into. You might do this if you have a pool of memory you want to use for constructing some objects of a class, but don't want to overload operator new for the whole class.

Related articles

Operator overloading

Understanding Pointers in C++

Dynamic Memory Allocation, Part 1: Advanced Memory Management

Dynamic Memory Allocation, Part 2: Dynamic Memory Allocation and Virtual Memory

Dynamic Memory Allocation, Part 4: Common Memory Management Problems in C++