w Inquiry.com - answers for IT professionals
 


 
Ask the C++ Pro 10-Minute Solutions

Overriding New and Delete
By Danny Kalev

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 feature—it 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.

 
Other 10-Minute Solutions
 How to Change the Mouse Pointer without Flicker
 Setting Full Row Selection in ListView Control
 Automating Type Conversions with stringstream Objects
 Improving Memory Reallocation with Vectors
 How to Use <fstream> Classes for File I/O
 Casting About for Safe Typecasting
 Overloading Operator + the Right Way
 How to Create Persistent Objects
 Making Linked Lists More User-Friendly
 Preventing Glitches in Signal Processing
 Forcing Object Allocation on the Free-store
 Using String-Based Data Validation
 Implementing the 'Resource Acquisition Is Initialization' Idiom
 Simple Locks for Data Files
 Template Specializations
 Exception Handling
 Using Bit Fields in Data Optimization
 Using the Transform() Algorithm to Change a String's Case
 Use RTTI for Dynamic Type Identification
 Choosing the Right swap () Implementation
 Take Charge and Initialize Your Own Data
 Share Data Among Objects Using the Monostate Design Pattern
 String Manipulation Made Easy with std::string Algorithms
 Using typedef to Curb Miscreant Code
 Managing Objects' Construction Order
 Bitwise Operators: Combining Efficiency and Ease of Use
 Use Function Adapters to Extend Generic Algorithms' Usage
 Simplify Callback Dispatching with Enumerated Indexes
 Streamline Your Bulk I/O Operations with Stream Iterators
 Optimize Your Member Layout
 Preserve Code Safety with Conversion Operators
 Modify Your Base Class Interface in Derived Classes
 Tackle Common Programming Tasks Using the New <tuple> Library
 Use Local Classes for Proper Cleanup in Exception-enabled Apps
 Use multimap to Create Associative Containers with Duplicate Keys
 Enforcing Compile-time Constraints
 Facilitate Directory Operations with the <dirent.h> and <dir.h> Libraries
 Spruce Up Your Built-in Arrays
 Target 32- and 64-bit Platforms Together with a Few Simple Datatype Changes
 Restrict Object Allocation to Specific Memory Types
 Use the Pimpl Idiom to Reduce Compilation Time and Enhance Encapsulation
 Automate Resource Management with shared_ptr
 The Quick and Dirty Way to Add
 Pointing to Class Members
 Detecting Keystrokes While Your Application is Busy
 Linked Lists
 Programming the System Tray
 Create a "Universal" DLL
 Convert Path to Long Path Name
 Constructing an Object at a Pre-Determined Memory Position
 Declaring Classes and Member Functions in a Namespace
 Using the auto_ptr Class Template to Facilitate Dynamic Memory Management
 Using the random_shuffle() Algorithm to Randomize a Sequence of Elements
 Defining a Function Object
 Implementing the Singleton Design Pattern
 Declaring Function Pointers and Implementing Callbacks
 Overloading Operator << for a User-Defined Type
 Implementing a Stopwatch Class for Performance Measurements
 Creating and Accessing Environment Variables
 Executing an Object's Member Function in a Separate Thread
 Creating Heterogeneous Containers
 Overriding New and Delete
 Time and Date Manipulation
  Defining Functions with a Variable Argument List
 Optimize Abstract Operations with Function Templates




Sponsored Links


Advertising Info  |   Member Services  |   Contact Us  |   Help  |   Feedback  |   Site Map
Jupiterweb networks

internet.comearthweb.comDevx.comClickZ

Search Jupiterweb:

Jupitermedia Corporation has four divisions:
JupiterWeb, JupiterResearch, JupiterEvents, and JupiterImages

Copyright 2004 Jupitermedia Corporation All Rights Reserved.
Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

Jupitermedia Corporate Info | Newsletters | Tech Jobs | E-mail Offers