Item 7: Be prepared for out-of-memory conditions.
When operator
new
can't allocate the memory you request, it throws an exception. (It used to return 0, and some older compilers still do that. You can make your compilers do it again if you want to, but I'll defer that discussion until the end of this Item.) Deep in your heart of hearts, you know that handling out-of-memory exceptions is the only truly moral course of action. At the same time, you are keenly aware of the fact that doing so is a pain in the neck. As a result, chances are that you omit such handling from time to time. Like always, perhaps. Still, you must harbor a lurking sense of guilt. I mean, what if new
really does yield an
You may think that one reasonable way to cope with this matter is to fall back on your days in the gutter, i.e., to use the preprocessor. For example, a common C idiom is to define a type-independent macro to allocate memory and then check to make sure the allocation succeeded. For C++, such a macro might look something like
#define NEW(PTR, TYPE) \ try { (PTR) = new TYPE; } \ catch (std::bad_alloc&) { assert(0); }
("Wait! What's this std::bad_alloc
business?", you ask. bad_alloc
is the type of exception operator
new
throws when it can't satisfy a memory allocation request, and std
is the name of the namespace (see Item 28) where bad_alloc
is defined. "Okay," you continue, "what's this assert
business?" Well, if you look in the standard C include file <assert.h>
(or its namespace-savvy C++ equivalent, <cassert>
see Item 49), you'll find that assert
is a macro. The macro checks to see if the expression it's passed is non-zero, and, if it's not, it issues an error message and calls abort
. Okay, it does that only when the standard macro NDEBUG
isn't defined, i.e., in debug mode. In production mode, i.e., when NDEBUG
is defined, assert
expands to nothing to a void
statement. You thus check assert
ions only when
This NEW
macro suffers from the common error of using an assert
to test a condition that might occur in production code (after all, you can run out of memory at any time), but it also has a drawback specific to C++: it fails to take into account the myriad ways in which new
can be used. There are three common syntactic forms for getting new objects of type T
, and you need to deal with the possibility of exceptions for each of these
new T;
new T(constructor arguments);
new T[size];
This oversimplifies the problem, however, because clients can define their own (overloaded) versions of operator
new
, so programs may contain an arbitrary number of different syntactic forms for using new
.
How, then, to cope? If you're willing to settle for a very simple error-handling strategy, you can set things up so that if a request for memory cannot be satisfied, an error-handling function you specify is called. This strategy relies on the convention that when operator
new
cannot satisfy a request, it calls a client-specifiable error-handling function often called a new-handler before it throws an exception. (In truth, what operator
new
really does is slightly more complicated. Details are provided in Item 8.)
To specify the out-of-memory-handling function, clients call set_new_handler
, which is specified in the header <new>
more or less like
typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw();
As you can see, new_handler
is a typedef for a pointer to a function that takes and returns nothing, and set_new_handler
is a function that takes and returns a new_handler
.
set_new_handler
's parameter is a pointer to the function operator
new
should call if it can't allocate the requested memory. The return value of set_new_handler
is a pointer to the function in effect for that purpose before set_new_handler
was
You use set_new_handler
like
// function to call if operator new can't allocate enough memory void noMoreMemory() { cerr << "Unable to satisfy request for memory\n"; abort(); }
int main() { set_new_handler(noMoreMemory);
int *pBigDataArray = new int[100000000];
...
}
If, as seems likely, operator
new
is unable to allocate space for 100,000,000 integers, noMoreMemory
will be called, and the program will abort after issuing an error message. This is a marginally better way to terminate the program than a simple core dump. (By the way, consider what happens if memory must be dynamically allocated during the course of writing the error message to cerr
...)
When operator
new
cannot satisfy a request for memory, it calls the new-handler function not once, but repeatedly until it can find enough memory. The code giving rise to these repeated calls is shown in Item 8, but this high-level description is enough to conclude that a well-designed new-handler function must do one of the
operator
new
's next attempt to allocate the memory to succeed. One way to implement this strategy is to allocate a large block of memory at program start-up, then release it the first time the new-handler is invoked. Such a release is often accompanied by some kind of warning to the user that memory is low and that future requests may fail unless more memory is somehow made available.
set_new_handler
). The next time operator
new
calls the new-handler function, it will get the one most recently installed. (A variation on this theme is for a new-handler to modify its own behavior, so the next time it's invoked, it does something different. One way to achieve this is to have the new-handler modify static or global data that affects the new-handler's behavior.)
set_new_handler
. With no new-handler installed, operator
new
will throw an exception of type std::bad_alloc
when its attempt to allocate memory is unsuccessful.
std::bad_alloc
or some type derived from std::bad_alloc
. Such exceptions will not be caught by operator
new
, so they will propagate to the site originating the request for memory. (Throwing an exception of a different type will violate operator
new
's exception specification. The default action when that happens is to call abort
, so if your new-handler is going to throw an exception, you definitely want to make sure it's from the std::bad_alloc
hierarchy. For more information on exception specifications, see Item M14.)
abort
or exit
, both of which are found in the standard C library (and thus in the standard C++ library see Item 49).
These choices give you considerable flexibility in implementing new-handler
Sometimes you'd like to handle memory allocation failures in different ways, depending on the class of the object being
class X { public: static void outOfMemory();
...
};
class Y { public: static void outOfMemory();
...
};
X* p1 = new X; // if allocation is unsuccessful, // call X::outOfMemory
Y* p2 = new Y; // if allocation is unsuccessful, // call Y::outOfMemory
C++ has no support for class-specific new-handlers, but it doesn't need to. You can implement this behavior yourself. You just have each class provide its own versions of set_new_handler
and operator
new
. The class's set_new_handler
allows clients to specify the new-handler for the class (just like the standard set_new_handler
allows clients to specify the global new-handler). The class's operator
new
ensures that the class-specific new-handler is used in place of the global new-handler when memory for class objects is
Consider a class X
for which you want to handle memory allocation failures. You'll have to keep track of the function to call when operator
new
can't allocate enough memory for an object of type X
, so you'll declare a static member of type new_handler
to point to the new-handler function for the class. Your class X
will look something like
class X { public: static new_handler set_new_handler(new_handler p); static void * operator new(size_t size);
private: static new_handler currentHandler; };
Static class members must be defined outside the class definition. Because you'll want to use the default initialization of static objects to 0, you'll define X::currentHandler
without initializing
new_handler X::currentHandler; // sets currentHandler // to 0 (i.e., null) by // default
The set_new_handler
function in class X
will save whatever pointer is passed to it. It will return whatever pointer had been saved prior to the call. This is exactly what the standard version of set_new_handler
new_handler X::set_new_handler(new_handler p) { new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; }
Finally, X
's operator
new
will do the
set_new_handler
with X
's error-handling
function. This will install X
's new-handler as the global new-
handler. In the code below, notice how you explicitly reference
the std
scope (where the standard set_new_handler
resides)
by using the "::
" notation.
operator
new
to actually allocate the requested memory. If the initial attempt at allocation fails, the global operator
new
will invoke X
's new-handler, because that function was just installed as the global new-handler. If the global operator
new
is ultimately unable to find a way to allocate the requested memory, it will throw a std::bad_alloc
exception, which X
's operator
new
will catch. X
's operator
new
will then restore the global new-handler that was originally in place, and it will return by propagating the exception.
operator
new
was able to successfully allocate enough memory for an object of type X
, X
's operator
new
will again call the standard set_new_handler
to restore the global error-handling function to what it was originally. It will then return a pointer to the allocated memory.
void * X::operator new(size_t size) { new_handler globalHandler = // install X's std::set_new_handler(currentHandler); // handler
void *memory;
try { // attempt memory = ::operator new(size); // allocation } catch (std::bad_alloc&) { // restore std::set_new_handler(globalHandler); // handler; throw; // propagate } // exception
std::set_new_handler(globalHandler); // restore // handler return memory; }
If the duplicated calls to std::set_new_handler
caught your eye, turn to Item M9 for information on how to eliminate
Clients of class X
use its new-handling capabilities like
void noMoreMemory(); // decl. of function to // call if memory allocation // for X objects fails
X::set_new_handler(noMoreMemory); // set noMoreMemory as X's // new-handling function
X *px1 = new X; // if memory allocation // fails, call noMoreMemory
string *ps = new string; // if memory allocation // fails, call the global // new-handling function // (if there is one)
X::set_new_handler(0); // set the X-specific // new-handling function // to nothing (i.e., null)
X *px2 = new X; // if memory allocation // fails, throw an exception // immediately. (There is // no new-handling function // for class X.)
You may note that the code for implementing this scheme is the same regardless of the class, so a reasonable inclination would be to reuse it in other places. As Item 41 explains, both inheritance and templates can be used to create reusable code. However, in this case, it's a combination of the two that gives you what you
All you have to do is create a "mixin-style" base class, i.e., a base class that's designed to allow derived classes to inherit a single specific capability in this case, the ability to set a class-specific new-handler. Then you turn the base class into a template. The base class part of the design lets derived classes inherit the set_new_handler
and operator
new
functions they all need, while the template part of the design ensures that each inheriting class gets a different currentHandler
data member. The result may sound a little complicated, but you'll find that the code looks reassuringly familiar. In fact, about the only real difference is that it's now reusable by any class that wants
template<class T> // "mixin-style" base class class NewHandlerSupport { // for class-specific public: // set_new_handler support
static new_handler set_new_handler(new_handler p); static void * operator new(size_t size);
private: static new_handler currentHandler; };
template<class T> new_handler NewHandlerSupport<T>::set_new_handler(new_handler p) { new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; }
template<class T> void * NewHandlerSupport<T>::operator new(size_t size) { new_handler globalHandler = std::set_new_handler(currentHandler);
void *memory;
try { memory = ::operator new(size); } catch (std::bad_alloc&) { std::set_new_handler(globalHandler); throw; }
std::set_new_handler(globalHandler);
return memory; }
// this sets each currentHandler to 0 template<class T> new_handler NewHandlerSupport<T>::currentHandler;
With this class template, adding set_new_handler
support to class X
is easy: X
just inherits from newHandlerSupport<X>
:
// note inheritance from mixin base class template. (See // my article on counting objects for information on why // private inheritance might be preferable here.) class X: public NewHandlerSupport<X> {
... // as before, but no declarations for }; // set_new_handler or operator new
Clients of X
remain oblivious to all the behind-the-scenes action; their old code continues to work. This is good, because one thing you can usually rely on your clients being is
Using set_new_handler
is a convenient, easy way to cope with the possibility of out-of-memory conditions. Certainly it's a lot more attractive than wrapping every use of new
inside a try
block. Furthermore, templates like NewHandlerSupport
make it simple to add a class-specific new-handler to any class that wants one. Mixin-style inheritance, however, invariably leads to the topic of multiple inheritance, and before starting down that slippery slope, you'll definitely want to read Item 43.
Until 1993, C++ required that operator
new
return 0 when it was unable to satisfy a memory request. The current behavior is for operator
new
to throw a std::bad_alloc
exception, but a lot of C++ was written before compilers began supporting the revised specification. The operator
new
(and operator
see Item 8) that continue to offer the traditional failure-yields-0 behavior. These forms are called "nothrow" forms because, well, they never do a throw
, and they employ nothrow
objects (defined in the standard header <new>
) at the point where new
is
class Widget { ... };
Widget *pw1 = new Widget; // throws std::bad_alloc if // allocation fails
if (pw1 == 0) ... // this test must fail
Widget *pw2 = new (nothrow) Widget; // returns 0 if allocation // fails
if (pw2 == 0) ... // this test may succeed
Regardless of whether you use "normal" (i.e., exception-throwing) new
or "nothrow" new
, it's important that you be prepared to handle memory allocation failures. The easiest way to do that is to take advantage of set_new_handler
, because it works with both