set-new-handler allows you to specify a function to be called when memory allocation requests cannot be satisfied.
The basic
If operator new can’t satisfy a memory allocation request, it will call a client-specifiable error-handling function called a new-handler before throwing a bad_alloc exception. To specify this out-of-memory-handling function, clients call set_new_handler, a standard library function declared in <new>:
|
|
The throw() here is an exception specification, telling us that function set_new_handler won’t throw any exceptions (though there’s more truth, refer to item 29).
set_new_handler’s parameter is the new new_handler we want to specify, which is a pointer to the function operator new should call if it fails to allocate the requested memory, and the return value if the previous new_handler. Once operator new fails to fulfill a memory request, it calls the new_handler function repeatedly until it succeeds to find enough memory (more details in item 51), so this default behavior gives us following conclusion - a well-designed new-handler function must do one of the following:
- Make more memory available to allow the next memory allocation attempt inside
operator newto succeed1. - Install a different new-handler that may be capable to make more memory 2. A variation is to modify the behavior of current
new-handler(via modifying static, namespace specific, or global data the affects the new-handler’s behavior) - Deinstall the new-handler, i.e., pass the null pointer to
set_new_handler, which leads tooperator newthrowing abad_allocexception. - Throw an exception of type
bad_allocor some type derived frombad_alloc, which will firstly be caught byoperator newand then propagate to the site originating the request for memory. - Not return, typically by calling
abortorexit.
For example, to use set-new-handler:
|
|
Customize the new-handler per class
C++ has no support for class-specific new-handlers, so we implement this by ourselves: we have each class provide its own versions of set_new_handler (which allows clients to specify the customized new-handler) and operator new (which ensures the class-specific new-handler is used in place of the global new-handler when memory allocation request fails).
To make thie ensurance confirmed, this class-specific operator new should do the following stuff:
- Call the standard
set_new_handlerto installWidget’s own error-handling function as the global new-handler. - Call the global
operator newto perform the actual memory allocation. Two things may happen during this step:- if allocation ultimately fails and a
bad_allocis thrown by the globaloperator new, restore the original global new-handler, and then propagate the exception - if allocation succeeds, return a pointer to the allocated memory, and then restore the original global new-handler prior to the call to
Widget::operator new.
- if allocation ultimately fails and a
To ensure that the original new-handler is always reinstalled, we can treat the global new-handler as a resource and follow the advice of item 13 to use resource-managing objects to prevent resource leaks:
|
|
Since the behavior of setting a class-specific new-handler is the same regardless of the class, we can reuse the setting procedure by creating a “mixin-style” base class (i.e., a base class that’s designed to allow derived classes to inherit a single specific capability), so that each derived class not only inherits the set_new_handler and operator new functions from the base class, but also gets a different class-specific new-handler from the template.
|
|
|
|
With this class template, adding set_new_handler support to Widget is easy: Widget just inherits from NewHandlerSupport<Widget>:
|
|
One interesting point for this design is that Widget inheriting from a templatized base class that takes Widget itself as a type parameter, and this technique has a straightforwart name: curiously recurring template pattern (CRTP).
Another point worth noting is that the NewHandlerSupport template never uses its type parameter T, because it doesn’t need to: all we need is a different copy of static data member currentHJandler for each class inheriting from NewHandlerSupport, and template parameter T just makes it possible to distinguish these derived classes - the template mechanism automatically generates a copy of currentHandler for each T with which NewHandlerSupport is instantiated.
Finally, let’s take a look at how to use the new-handling capabilities in Widget:
|
|
Traditional failure-yields-null alternative for operator new
Althought now it is specified to throw a bad_alloc exception, until 1993, C++ required that operator new return null when it way unable to allocate the requested memory. Unwilling to abandon the traditional behavior, the C++ standardization committee provided an alternative form of operator new called “nothrow” forms, in part because the forms employ nothrow objects defined in the header <new>:
|
|
However, using new (std::nothrow) Widget only guarantees that operator new won’t throw, not the whole expression: after the nothrow versoin of operator new succeeds to allocate enough memory for a Widget object, and then the Widget constructor is called, chances are that the Widget constructor might itself new up some memory and throw, and then the exception will be propagated as usual.
Conclusion: we never have a need for nothrow new.
-
One way to achieve this strategy is to allocate a large block of memory at program start-up, then release it for use in the program the first time the new-handler is invoked. ↩︎
-
The strategy may be implemented by calling
set_new_handlerinside currentnew-handler. The next timeoperator newcall thenew-handlerfunction, it will get the one most recently installed. ↩︎