Class-Specific new and delete Operators

You can also write versions of the new and delete operators that are specific to a particular class. This lets you perform memory management that is customized for a class's individual characteristics.

For example, you might know that there will never be more than a certain small number of instances of a class at any one time, but they'll be allocated and deallocated frequently. You can use this information to write class-specific versions of new and delete that work faster than the global version.You can declare an array large enough to hold all the instances of the class, and then have new and delete manage the array.

To write class-specific new and delete operators, you declare member functions named operator new and operator delete. These operators take precedence over the global new and delete operators, in the same way that any member function takes precedence over a global function with the same name. These operators are called whenever you dynamically allocate objects of that class. For example:

// Class-specific new and delete operators

#include <iostream.h>

#include <string.h>

#include <stddef.h>

const int MAXNAMES = 100;

class Name

{

public:

Name( const char *s ) { strncpy( name, s, 25 ); }

void display() const { cout << '\n' << name; }

void *operator new( size_t size );

void operator delete( void *ptr );

~Name() {}; // do-nothing destructor

private:

char name[25];

};

// -------- Simple memory pool to handle fixed number of Names

char pool[MAXNAMES] [sizeof( Name )];

int inuse[MAXNAMES];

// -------- Overloaded new operator for the Name class

void *Name::operator new( size_t size )

{

for( int p = 0; p < MAXNAMES; p++ )

if( !inuse[p] )

{

inuse[p] = 1;

return pool + p;

}

return 0;

}

// --------- Overloaded delete operator for the Names class

void Name::operator delete( void *ptr )

{

inuse[((char *)ptr - pool[0]) / sizeof( Name )] = 0;

}

void main()

{

Name *directory[MAXNAMES];

char name[25];

for( int i = 0; i < MAXNAMES; i++ )

{

cout << "\nEnter name # " << i+1 << ": ";

cin >> name;

directory[i] = new Name( name );

}

for( i = 0; i < MAXNAMES; i++ )

{

directory[i]->display();

delete directory[i];

}

}

This program declares a global array called pool that can store all the Name objects expected. There is also an associated integer array called inuse, which contains true/false flags that indicate whether the corresponding entry in the pool is in use.

When the statement directory[i] = new Name( name ) is executed, the compiler calls the class's new operator. The new operator finds an unused entry in pool, marks it as used, and returns its address. Then the compiler calls Name's constructor, which uses that memory and initializes it with a character string. Finally, a pointer to the resulting object is assigned to an entry in directory.

When the statement delete directory[i] is executed, the compiler calls Name 's destructor. In this example, the destructor does nothing; it is defined only as a placeholder. Then the compiler calls the class's delete operator. The delete operator finds the specified object's location in the array and marks it as unused, so the space is available for subsequent allocations.

Note that new is called before the constructor, and that delete is called after the destructor. The following example illustrates this more clearly by printing messages when each function is called:

// Class-specific new and delete operators with constructor, destructor

#include <iostream.h>

#include <malloc.h>

class Name

{

public:

Name() { cout << "\nName's constructor running"; }

void *operator new( size_t size );

void operator delete( void *ptr );

~Name() { cout << "\nName's destructor running"; }

private:

char name[25];

};

// -------- Simple memory pool to handle one Name

char pool[sizeof( Name )];

// -------- Overloaded new operator for the Name class

void *Name::operator new( size_t )

{

cout << "\nName's new running";

return pool;

}

// --------- Overloaded delete operator for the Name class

void Name::operator delete( void *p )

{

cout << "\nName's delete running";

}

void main()

{

cout << "\nExecuting: nm = new Name";

Name *nm = new Name;

cout << "\nExecuting: delete nm";

delete nm;

}

The following example does nothing with the class except display the following messages as the various functions execute:

Executing: nm = new Name

Name's new running

Name's constructor running

Executing: delete nm

Name's destructor running

Name's delete running

One consequence of the order in which new and delete are called is that they are static member functions, even if they are not declared with the static keyword. This is because the new operator is called before the class's constructor is called; the object does not exist yet, so it would be meaningless for new to access any of its members. Similarly, the delete operator is called after the destructor is called, and the object no longer exists. To prevent new and delete from accessing any nonstatic members, the operators are always considered static member functions.

The class-specific new and delete operators are not called when you allocate or deallocate an array of objects; instead the global new and delete are called for array allocations. You can explicitly call the global versions of the operators when you allocate a single object by using the scope resolution operator (::). For example:

Name *nm = ::new Name; // Use global new

If you have also redefined the global new operator, this syntax calls your version of the operator. The same syntax works for delete.