For most programming tasks, the default implementation of operators new and delete is sufficient. In fact, many C++ programmers get along without even knowing they can override these operators. However, in certain application domains and advanced programming tasks, redefining these operators for a given class is essential, e.g., when you want to ensure that all instances of that class are allocated from a custom memory pool instead of being allocated on the free-store (although you can use placement new for this purpose, it doesn't block users from using global overriding new and delete).
There are other uses for overriding new and delete such as implementing a garbage collector and memory leak detectors. Furthermore, certain applications require that objects be allocated on a specific memory address, e.g., a video card's internal buffer. For all these applications, overriding new and delete is necessary. I will show how to override these operators, first on a per-class basis, and then globally.
Step 1: Declaring New and Delete as Class Members
Suppose we want to override new and delete for class C. Our first step is to declare these operators as C's member functions:
#include <new> // for size_t
class C
{
public:
C();
~C();
static void* operator new (size_t size);
static void operator delete (void *p);
};
The new and delete member functions are implicitly declared static. Nevertheless, we declare them static to document this property explicitly.
Step 2: Implementation
The implementation of new is straightforward: it calls a custom allocation function, say, allocate_from_pool(), and returns the resulting pointer. You can use any other memory allocation function that suits your needs, e.g., malloc(), GlobalAlloc(), etc. Here's the definition of the overriding new:
void* C::operator new (size_t size)
{
void *p=allocate_from_pool(size);
return p;
} // C's default constructor implicitly called here
When new exits, the class's default constructor executes automatically and constructs the object.
The matching version of delete is as follows:
void C::operator delete (void *p)
{
release(p); // return memory to pool
} // C's destructor implicitly called at this point
C++ guarantees that an object's destructor is automatically called right before delete executes. Therefore, we don't invoke C's destructor explicitly (doing so would cause undefined behavior as the destructor will actually run twice).
Additional Customizations
Overriding new enables us to customize its functionality in various ways. For instance, we can add an error handling mechanism to our implementation by defining a special exception class, mem_exception. An object of this class will be thrown in case of a failure:
class mem_exception {};
void* C::operator new (size_t size)
{
void *p=allocate_from_pool(size);
if (p==0)
throw mem_exception();
return p;
}
In a similar vein, we can extend delete's functionality to report the amount of available memory, write a message to a log file, and so on:
#include <ctime> // for time()
void C::operator delete (void *p)
{
release(p);
cout << "available pool size: " << get_pool_size();
write_to_log("deletion occurred at: ", time(0));
}
Step 3: Using the Overriding Operators
We use the overriding new and delete as we use the default new and delete; C++ automatically chooses the overriding versions if they exist. Classes for which no overriding versions are defined continue to use the default new and delete:
int main()
{
C *p=new C; // C::new
delete p; // C::delete
int *p=new int; // ::new
delete p; // ::delete
}
Step 4: Overriding New and Delete Globally
Up until now, we've focused on a class-based override. What if we want override new and delete not just for one class, but for the entire application? C++ allows us to do that, too, by defining global versions of these operators. Unlike a class-based override, the global new and delete are declared in the global namespace. This technique may be particularly useful for Visual C++ users. As you probably know, the implementation of new in Visual C++ isn't standard compliant: new returns a null pointer upon failure instead of throwing a std::bad_alloc exception, as required by the C++ ANSI/ISO standard. The following example overrides global new and delete to force standard compliant behavior. Visual C++ users wishing to fix their laggard implementation can use it:
#include <exception> // for std::bad_alloc
#include <new>
#include <cstdlib> // for malloc() and free()
// Visual C++ fix of operator new
void* C::operator new (size_t size)
{
void *p=malloc(size);
if (p==0) // did malloc succeed?
throw std::bad_alloc(); // ANSI/ISO compliant behavior
return p;
}
The matching delete is as follows:
void operator delete (void *p)
{
free(p);
}
Now you can use try and catch blocks to handle potential allocation exceptions properly, even if your compiler isn't yet fully compliant:
int main()
{
try
{
int *p=new int[10000000]; // user-defined new
//..use p
delete p; // user-defined delete
}
catch (std::bad_alloc & x)
{
std::cout << x.what(); // display exception
}
}
Very few programming languages allow you to access and modify their inner-workings as does C++. Indeed, overriding new and delete is a very powerful featureit gives you tighter control over the language's memory management policy and enables you to extend its functionality in various ways. However, the default implementation of new and delete is suitable for most purposes; don't override these operators unless you have compelling reasons to do so, and when doing so, remember always to override both of them.