Overloading New in C++

Pooled Memory Allocation

Most large C++ programs make use of dynamically allocated memory. This is especially true of EDA applications (simulators and tools for VLSI chip design), where the designer can never safely set an upper limit on application size. In many cases large amounts of dynamically allocated memory is consumed by interconnected objects which are not themselves very large. The time consumed allocating objects can be minimized, but is unavoidable. A significant amount of processing time can also be consumed traversing the dynamic data structures and returning them to the system using the default C++ delete function. Time consuming memory recovery can be avoided by using a pool based memory allocator.

A pool based memory allocator allocates large blocks of memory and then allocates smaller objects from these blocks. When it is time to recover memory, the entire pool is deallocated at once. This usually involves returning only a few large memory blocks to the system. This greatly reduces the time consumed by memory recovery.

Object Allocation and Initialization

If a C++ object is allocated from a memory pool, its constructor will not be called by default. This can sometimes be handled by initializing the object with an explicit call to the constructor. For example:


  my_class *pMyClass;

  pMyClass = (my_class *)pool.GetMem( sizeof( my_class ) );
  *pMyClass = my_class();  // invoke the constructor

This approach has several problems. The constructor invokation above creates a temporary instance of the class my_class. This class is then copied into the memory pointed to by pMyClass. After the class is copied, the destructor for my_class will be called. If memory allocation takes place in the constructor and deallocation takes place in the destructor, there can be a problem. For example, if the class pointed to by pMyClass is initialized with a pointer the memory allocated by the my_class constructor, this same memory will be deallocated by the destructor. When the assignment completes, the class pointed to by pMyClass will point to deallocated memory, which is not what the programmer intended.

The problem above can be handled by adding init and dealloc class functions which can be called explicitly to allocate memory. The init function can be called after the class constructor copy.

  my_class *pMyClass;

  pMyClass = (my_class *)pool.GetMem( sizeof( my_class ) );
  *pMyClass = my_class();  // invoke the constructor

  pMyClass->init();  // allocateion memory

Unfortunately, the scheme outlined above is totally inadequate if the class have virtual functions. The class constructor will not initialize the virtual function table with the appropriate function addresses.

Overloading New

Since at least 1993 C++ has defined a way to overload the new operator for a given class. By providing an overloaded verion of new for a class (e.g., my_class above), there will be a simple and natural way to allocate an object from a memory pool and initialize it properly. The overloaded new function allocates memory from the memory pool and the C++ compiler generates code to initialize the virtual function table and to invoke the class constructor.

The C++ code below has a base class (cleverly named base) and a derived class base_one. The base class has an overloaded version of new, which takes two arguments: the number of bytes to allocate and a pointer to a memory pool allocation object. The compiler automatically plugs in the type size (the first argument to the overloaded new). The call to new then takes the form

    pClass = new( user args ) type

which is expanded into a call to the overloaded new function

    void *operator new( type size, user args );

For more details see section 5.3.3 of the ANSI C++ standard.


class base {
public:
    base() { }

    void *operator new( unsigned int num_bytes, pool *mem)
    {
	return mem->GetMem( num_bytes);
    }

    virtual void pr(void) = 0;
};


class base_one : public base {
private:
    int a;
public:
    base_one() {}

    void pr(void) 
    {
       // local print
    }
};


main()
{
    base *pB1;
    pool mem;

    pB1 = new( &mem ) base_one;
    pB1->pr();
}

In this example there is a memory allocation type pool. This is passed as an argument to new, which uses the GetMem class function to allocate memory.

A complete test case, demonstrating overloading of the new operator to allocate a class with virtual functions is shown here.

Portability Issues

The complexity of C++ and the obscurity of the language standard in some areas takes the problems encountered in portability to new levels.

With older compilers from HP and IBM, use of the "positional new", where the operator new is passed a memory pool argument, as shown above, requires an overloaded version of the default new as well. Note that this is not required by Solaris C++ or current releases of HP or IBM C++. In the code shown below, the first function overloads the default version of operator new. Since this class will allocate memory from a pool, this version should never be used and contains an assert. The second version of the new operator allocates memory from the memory pool.


#ifdef _BRAIN_DAMAGED_IBM_
    // IBM requires that the operator new size argument be unsigned long
    void *operator new( unsigned long num_bytes )
#else
    void *operator new( unsigned int num_bytes )
#endif
    {
	assert( FALSE );
	return NULL;
    }

#ifdef _BRAIN_DAMAGED_IBM_
    void *operator new( unsigned long num_bytes, pool *mem )
#else
    void *operator new( unsigned int num_bytes, pool *mem )
#endif
    {
	return mem->GetMem( num_bytes );
    } // operator new

    // There is no delete, since memory is recovered through
    // deallocation of the memory pool      
    void operator delete( void * ) { /* do nothing */ } 

The code shown above will compile on Sun and on the earlier versions of the IBM and HP compilers. Of course when this code is compiled on IBM the -D_BRAIN_DAMAGED_IBM_ flag must be used, since IBM requires that the num_bytes argument be unsigned long.

Ian Kaplan
Last updated May 1998


back to Notes on Software and Software Engineering

back to home page