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 new
to 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 new
throwing abad_alloc
exception. - Throw an exception of type
bad_alloc
or some type derived frombad_alloc
, which will firstly be caught byoperator new
and then propagate to the site originating the request for memory. - Not return, typically by calling
abort
orexit
.
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_handler
to installWidget
’s own error-handling function as the global new-handler. - Call the global
operator new
to perform the actual memory allocation. Two things may happen during this step:- if allocation ultimately fails and a
bad_alloc
is 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_handler
inside currentnew-handler
. The next timeoperator new
call thenew-handler
function, it will get the one most recently installed. ↩︎