- /*
- effective C++
- Shifting from C to C++
- Item 1: Prefer const and inline to #define.
- Item 2: Prefer <iostream> to <stdio.h>.
- Item 3: Prefer new and delete to malloc and free.
- Item 4: Prefer C++-style comments.
- Memory Management
- Item 5: Use the same form in corresponding uses of new and delete.
- Item 6: Use delete on pointer members in destructors.
- */
- Item 7: Be prepared for out-of-memory conditions.
- To specify the out-of-memory-handling function, clients call set_new_handler, which is specified in the header <new> more or less like this:
- 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 called.
- You use set_new_handler like this:
- // 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];
- ...
- }
- Sometimes you'd like to handle memory allocation failures in different ways, depending on the class of the object being allocated:
- 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
- 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 this:
- 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 it:
- 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 does:
- new_handler X::set_new_handler(new_handler p)
- {
- new_handler oldHandler = currentHandler;
- currentHandler = p;
- return oldHandler;
- }
- Here's how you say all that in C++:
- 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;
- }
- Clients of class X use its new-handling capabilities like this:
- 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.)
- 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 oblivious.