More Books
Effective C++ 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Table of Contents
Copyright
Praise for Effective C++, Third Edition
Addison-Wesley Professional Computing Series
Preface
Acknowledgments
Introduction
Terminology
Chapter 1. Accustoming Yourself to C++
Item 1: View C++ as a federation of languages
Item 2: Prefer consts, enums, and inlines to #defines
Item 3: Use const whenever possible
Item 4: Make sure that objects are initialized before they're used
Chapter 2. Constructors, Destructors, and Assignment Operators
Item 5: Know what functions C++ silently writes and calls
Item 6: Explicitly disallow the use of compiler-generated functions you do not want
Item 7: Declare destructors virtual in polymorphic base classes
Item 8: Prevent exceptions from leaving destructors
Item 9: Never call virtual functions during construction or destruction
Item 10: Have assignment operators return a reference to *this
Item 11: Handle assignment to self in operator=
Item 12: Copy all parts of an object
Chapter 3. Resource Management
Item 13: Use objects to manage resources.
Item 14: Think carefully about copying behavior in resource-managing classes.
Item 15: Provide access to raw resources in resource-managing classes.
Item 16: Use the same form in corresponding uses of new and delete.
Item 17: Store newed objects in smart pointers in standalone statements.
Chapter 4. Designs and Declarations
Item 18: Make interfaces easy to use correctly and hard to use incorrectly
Item 19: Treat class design as type design
Item 20: Prefer pass-by-reference-to-const to pass-by-value
Item 21: Don't try to return a reference when you must return an object
Item 22: Declare data members private
Item 23: Prefer non-member non-friend functions to member functions
Item 24: Declare non-member functions when type conversions should apply to all parameters
Item 25: Consider support for a non-throwing swap
Chapter 5. Implementations
Item 26: Postpone variable definitions as long as possible.
Item 27: Minimize casting.
Item 28: Avoid returning "handles" to object internals.
Item29: Strive for exception-safe code.
Item 30: Understand the ins and outs of inlining.
Item31: Minimize compilation dependencies between files.
Chapter 6. Inheritance and Object-Oriented Design
Item 32: Make sure public inheritance models "is-a."
Item 33: Avoid hiding inherited names
Item 34: Differentiate between inheritance of interface and inheritance of implementation
Item 35: Consider alternatives to virtual functions
Item 36: Never redefine an inherited non-virtual function
Item 37: Never redefine a function's inherited default parameter value
Item 38: Model "has-a" or "is-implemented-in-terms-of" through composition
Item 39: Use private inheritance judiciously
Item 40: Use multiple inheritance judiciously
Chapter 7. Templates and Generic Programming
Item 41: Understand implicit interfaces and compile-time polymorphism
Item 42: Understand the two meanings of typename
Item 43: Know how to access names in templatized base classes
Item 44: Factor parameter-independent code out of templates
Item 45: Use member function templates to accept "all compatible types."
Item 46: Define non-member functions inside templates when type conversions are desired
Item 47: Use traits classes for information about types
Item 48: Be aware of template metaprogramming
Chapter 8. Customizing new and delete
Item 49: Understand the behavior of the new-handler
Item 50: Understand when it makes sense to replace new and delete
Item 51: Adhere to convention when writing new and delete
Item 52: Write placement delete if you write placement new
Chapter 9. Miscellany
Item 53: Pay attention to compiler warnings.
Item 54: Familiarize yourself with the standard library, including TR1
Item.55: Familiarize yourself with Boost.
Appendix A. Beyond Effective C++
Appendix B. Item Mappings Between Second and Third Editions
Index
index_SYMBOL
index_A
index_B
index_C
index_D
index_E
index_F
index_G
index_H
index_I
index_J
index_K
index_L
index_M
index_N
index_O
index_P
index_R
index_S
index_T
index_U
index_V
index_W
index_X
index_Z

Item 49: Understand the behavior of the new-handler

When operator new can't satisfy a memory allocation request, it throws an exception. Long ago, it returned a null pointer, and some older compilers still do that. You can still get the old behavior (sort of), but I'll defer that discussion until the end of this Item.

Before operator new throws an exception in response to an unsatisfiable request for memory, it calls a client-specifiable error-handling function called a new-handler. (This is not quite true. What operator new really does is a bit more complicated. Details are provided in Item 51.) To specify the out-of-memory-handling function, clients call set_new_handler, a standard library function declared in <new>:


namespace std {



  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. (The "throw()" at the end of set_new_handler's declaration is an exception specification. It essentially says that this function won't throw any exceptions, though the truth is a bit more interesting. For details, see Item 29.)

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 outOfMem()

{

  std::cerr << "Unable to satisfy request for memory\n";

  std::abort();

}

int main()

{

  std::set_new_handler(outOfMem);

  int *pBigDataArray = new int[100000000L];

  ...

}


If operator new is unable to allocate space for 100,000,000 integers, outOfMem will be called, and the program will abort after issuing an error message. (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 is unable to fulfill a memory request, it calls the new-handler function repeatedly until it can find enough memory. The code giving rise to these repeated calls is shown in Item 51, but this high-level description is enough to conclude that a well-designed new-handler function must do one of the following:

  • Make more memory available. This may allow the next memory allocation attempt inside operator new to succeed. One way to implement 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.

  • Install a different new-handler. If the current new-handler can't make any more memory available, perhaps it knows of a different new-handler that can. If so, the current new-handler can install the other new-handler in its place (by calling 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, namespace-specific, or global data that affects the new-handler's behavior.)

  • Deinstall the new-handler, i.e., pass the null pointer to set_new_handler. With no new-handler installed, operator new will throw an exception when memory allocation is unsuccessful.

  • Throw an exception of type bad_alloc or some type derived from bad_alloc. Such exceptions will not be caught by operator new, so they will propagate to the site originating the request for memory.

  • Not return, typically by calling abort or exit.

These choices give you considerable flexibility in implementing new-handler functions.

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


C++ has no support for class-specific new-handlers, but it doesn't need any. 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 (exactly 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 allocated.

Suppose you want to handle memory allocation failures for the Widget class. You'll have to keep track of the function to call when operator new can't allocate enough memory for a Widget object, so you'll declare a static member of type new_handler to point to the new-handler function for the class. Widget will look something like this:


class Widget {

public:

  static std::new_handler set_new_handler(std::new_handler p) throw();

  static void * operator new(std::size_t size) throw(std::bad_alloc);

private:

  static std::new_handler currentHandler;

};


Static class members must be defined outside the class definition (unless they're const and integral—see Item 2), so:


std::new_handler Widget::currentHandler = 0;    // init to null in the class

                                                // impl. file


The set_new_handler function in Widget will save whatever pointer is passed to it, and it will return whatever pointer had been saved prior to the call. This is what the standard version of set_new_handler does:


std::new_handler Widget::set_new_handler(std::new_handler p) throw()

{

  std::new_handler oldHandler = currentHandler;

  currentHandler = p;

  return oldHandler;

}


Finally, Widget's operator new will do the following:

  1. Call the standard set_new_handler with Widget's error-handling function. This installs Widget's new-handler as the global new-handler.

  2. Call the global operator new to perform the actual memory allocation. If allocation fails, the global operator new invokes Widget's new-handler, because that function was just installed as the global new-handler. If the global operator new is ultimately unable to allocate the memory, it throws a bad_alloc exception. In that case, Widget's operator new must restore the original global new-handler, then propagate the exception. To ensure that the original new-handler is always reinstated, Widget treats the global new-handler as a resource and follows the advice of Item 13 to use resource-managing objects to prevent resource leaks.

  3. If the global operator new was able to allocate enough memory for a Widget object, Widget's operator new returns a pointer to the allocated memory. The destructor for the object managing the global new-handler automatically restores the global new-handler to what it was prior to the call to Widget's operator new.

Here's how you say all that in C++. We'll begin with the resource-handling class, which consists of nothing more than the fundamental RAII operations of acquiring a resource during construction and releasing it during destruction (see Item 13):


class NewHandlerHolder {

public:

  explicit NewHandlerHolder(std::new_handler nh)    // acquire current

  :handler(nh) {}                                   // new-handler



  ~NewHandlerHolder()                               // release it

  { std::set_new_handler(handler); }

private:

  std::new_handler handler;                         // remember it



  NewHandlerHolder(const NewHandlerHolder&);        // prevent copying

  NewHandlerHolder&                                 // (see Item 14)

   operator=(const NewHandlerHolder&);

};


This makes implementation of Widget's operator new quite simple:


void * Widget::operator new(std::size_t size) throw(std::bad_alloc)

{

  NewHandlerHolder                              // install Widget's

   h(std::set_new_handler(currentHandler));     // new-handler



  return ::operator new(size);                  // allocate memory

                                                // or throw



}                                               // restore global

                                                // new-handler


Clients of Widget use its new-handling capabilities like this:


void outOfMem();                   // decl. of func. to call if mem. alloc.

                                   // for Widget objects fails



Widget::set_new_handler(outOfMem); // set outOfMem as Widget's

                                   // new-handling function



Widget *pw1 = new Widget;          // if memory allocation

                                   // fails, call outOfMem



std::string *ps = new std::string; // if memory allocation fails,

                                   // call the global new-handling

                                   // function (if there is one)



Widget::set_new_handler(0);        // set the Widget-specific

                                   // new-handling function to

                                   // nothing (i.e., null)



Widget *pw2 = new Widget;          // if mem. alloc. fails, throw an

                                   // exception immediately. (There is

                                   // no new- handling function for

                                   // class Widget.)


The code for implementing this scheme is the same regardless of the class, so a reasonable goal would be to reuse it in other places. An easy way to make that possible is to 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 turn the base class into a template, so that you get a different copy of the class data for each inheriting class.

The base class part of this 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. That may sound a bit complicated, but the code looks reassuringly familiar. In fact, the only real difference is that it's now available to any class that wants it:


template<typename T>              // "mixin-style" base class for

class NewHandlerSupport{          // class-specific set_new_handler

public:                           // support



  static std::new_handler set_new_handler(std::new_handler p) throw();

  static void * operator new(std::size_t size) throw(std::bad_alloc);



  ...                             // other versions of op. new —

                                  // see Item 52

private:

  static std::new_handler currentHandler;

};



template<typename T>

std::new_handler

NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()

{

 std::new_handler oldHandler = currentHandler;

 currentHandler = p;

 return oldHandler;

}



template<typename T>

void* NewHandlerSupport<T>::operator new(std::size_t size)

  throw(std::bad_alloc)

{

  NewHandlerHolder h(std::set_new_handler(currentHandler));

  return ::operator new(size);

}

// this initializes each currentHandler to null

template<typename T>

std::new_handler NewHandlerSupport<T>::currentHandler = 0;


With this class template, adding set_new_handler support to Widget is easy: Widget just inherits from NewHandlerSupport<Widget>. (That may look peculiar, but I'll explain in more detail below exactly what's going on.)


class Widget: public NewHandlerSupport<Widget> {

  ...                          // as before, but without declarations for

};                             // set_new_handler or operator new


That's all Widget needs to do to offer a class-specific set_new_handler.

But maybe you're still fretting over Widget inheriting from NewHandlerSupport<Widget>. If so, your fretting may intensify when you note that the NewHandlerSupport template never uses its type parameter T. It doesn't need to. All we need is a different copy of NewHandlerSupport — in particular, its static data member currentHandler — for each class that inherits from NewHandlerSupport. The template parameter T just distinguishes one inheriting class from another. The template mechanism itself automatically generates a copy of currentHandler for each T with which NewHandlerSupport is instantiated.

As for Widget inheriting from a templatized base class that takes Widget as a type parameter, don't feel bad if the notion makes you a little woozy. It initially has that effect on everybody. However, it turns out to be such a useful technique, it has a name, albeit one that reflects the fact that it looks natural to no one the first time they see it. It's called the curiously recurring template pattern (CRTP). Honest.

At one point, I published an article suggesting that a better name would be "Do It For Me," because when Widget inherits from NewHandlerSupport<Widget>, it's really saying, "I'm Widget, and I want to inherit from the NewHandlerSupport class for Widget." Nobody uses my proposed name (not even me), but thinking about CRTP as a way of saying "do it for me" may help you understand what the templatized inheritance is doing.

Templates like NewHandlerSupport make it easy 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 path, you'll want to read Item 40.

Until 1993, C++ required that operator new return null when it was unable to allocate the requested memory. operator new is now specified to throw a bad_alloc exception, but a lot of C++ was written before compilers began supporting the revised specification. The C++ standardization committee didn't want to abandon the test-for-null code base, so they provided alternative forms of operator new that offer the traditional failure-yields-null behavior. These forms are called "nothrow" forms, in part because they employ nothrow objects (defined in the header <new>) at the point where new is used:


class Widget { ... };

Widget *pw1 = new Widget;                 // throws bad_alloc if

                                          // allocation fails



if (pw1 == 0) ...                         // this test must fail



Widget *pw2 =new (std::nothrow) Widget;   // returns 0 if allocation for

                                          // the Widget fails



if (pw2 == 0) ...                         // this test may succeed


Nothrow new offers a less compelling guarantee about exceptions than is initially apparent. In the expression "new (std::nothrow) Widget," two things happen. First, the nothrow version of operator new is called to allocate enough memory for a Widget object. If that allocation fails, operator new returns the null pointer, just as advertised. If it succeeds, however, the Widget constructor is called, and at that point, all bets are off. The Widget constructor can do whatever it likes. It might itself new up some memory, and if it does, it's not constrained to use nothrow new. Although the operator new call in "new (std::nothrow) Widget" won't throw, then, the Widget constructor might. If it does, the exception will be propagated as usual. Conclusion? Using nothrow new guarantees only that operator new won't throw, not that an expression like "new (std::nothrow) Widget" will never yield an exception. In all likelihood, you will never have a need for nothrow new.

Regardless of whether you use "normal" (i.e., exception-throwing) new or its somewhat stunted nothrow cousin, it's important that you understand the behavior of the new-handler, because it's used with both forms.

Things to Remember

  • set_new_handler allows you to specify a function to be called when memory allocation requests cannot be satisfied.

  • Nothrow new is of limited utility, because it applies only to memory allocation; subsequent constructor calls may still throw exceptions.