Chapter 8. Customizing new and delete

来源:互联网 发布:php https curl 编辑:程序博客网 时间:2024/05/16 09:29

1. Static class members(member variable and function) must be defined outside the class definition (unless they'reconst and integral

2. Set a user specific handler for new.

namespace std {  typedef void (*new_handler)();  new_handler set_new_handler(new_handler p) throw();}

// function to call if operator new can't allocate enough memoryvoid outOfMem(){  std::cerr << "Unable to satisfy request for memory\n";  std::abort();}int main(){  std::set_new_handler(outOfMem);  int *pBigDataArray = new int[100000000L];  ...}

3. Create Class Specific "new " operator and "new handler":

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'reconst and integral—seeItem 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 ofset_new_handler does:

std::new_handler Widget::set_new_handler(std::new_handler p) throw(){  std::new_handler oldHandler = currentHandler;  currentHandler = p;  return oldHandler;}

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'soperator 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
template<typename T>              // "mixin-style" base class forclass NewHandlerSupport{          // class-specific set_new_handlerpublic:                           // 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 52private:  static std::new_handler currentHandler;};template<typename T>std::new_handlerNewHandlerSupport<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 nulltemplate<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 fromNewHandlerSupport<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-specificset_new_handler.

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

As for Widget inheriting from a templatized base class that takesWidget 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 thecuriously recurring template pattern (CRTP). Honest.

4.

4. 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 abad_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 ofoperator new that offer the traditional failure-yields-null behavior. These forms are called "nothrow" forms, in part because they employnothrow objects (defined in the header<new>) at the point wherenew is used:

class Widget { ... };Widget *pw1 = new Widget;                 // throws bad_alloc if                                          // allocation failsif (pw1 == 0) ...                         // this test must failWidget *pw2 =new (std::nothrow) Widget;   // returns 0 if allocation for                                          // the Widget failsif (pw2 == 0) ...                         // this test may succeed

5. Create custom specific  NEW and Delete:

Purpose:

  • To detect usage errors. Failure todelete memory conjured up bynew leads to memory leaks. Using more than onedelete onnewed memory yields undefined behavior. Ifoperator new keeps a list of allocated addresses andoperator delete removes addresses from the list, it's easy to detect such usage errors. Similarly, a variety of programming mistakes can lead to data overruns (writing beyond the end of an allocated block) and underruns (writing prior to the beginning of an allocated block). Customoperator news can overallocate blocks so there's room to put known byte patterns ("signatures") before and after the memory made available to clients.operator deletes can check to see if the signatures are still intact. If they're not, an overrun or underrun occurred sometime during the life of the allocated block, andoperator delete can log that fact, along with the value of the offending pointer.

  • To improve efficiency. The versions ofoperator new andoperator delete that ship with compilers are designed for general-purpose use. They have to be acceptable for long-running programs (e.g., web servers), but they also have to be acceptable for programs that execute for less than a second. They have to handle series of requests for large blocks of memory, small blocks, and mixtures of the two. They have to accommodate allocation patterns ranging from the dynamic allocation of a few blocks that exist for the duration of the program to constant allocation and deallocation of a large number of short-lived objects. They have to worry about heap fragmentation, a process that, if unchecked, eventually leads to the inability to satisfy requests for large blocks of memory, even when ample free memory is distributed across many small blocks.

    Given the demands made on memory managers, it's no surprise that theoperator news andoperator deletes that ship with compilers take a middle-of-the-road strategy. They work reasonably well for everybody, but optimally for nobody. If you have a good understanding of your program's dynamic memory usage patterns, you can often find that custom versions ofoperator new and operator delete outperform the default ones. By "outperform," I mean they run faster — sometimes orders of magnitude faster — and they require less memory — up to 50% less. For some (though by no means all) applications, replacing the stock new and delete with custom versions is an easy way to pick up significant performance improvements.

  • To collect usage statistics. Before heading down the path of writing customnews anddeletes, it's prudent to gather information about how your software uses its dynamic memory. What is the distribution of allocated block sizes? What is the distribution of their lifetimes? Do they tend to be allocated and deallocated in FIFO ("first in, first out") order, LIFO ("last in, first out") order, or something closer to random order? Do the usage patterns change over time, e.g., does your software have different allocation/deallocation patterns in different stages of execution? What is the maximum amount of dynamically allocated memory in use at any one time (i.e., its "high water mark")? Custom versions ofoperator new andoperator delete make it easy to collect this kind of information.

  • To detect usage errors (as above).

  • To collect statistics about the use of dynamically allocated memory (also as above).

  • To increase the speed of allocation and deallocation. General-purpose allocators are often (though not always) a lot slower than custom versions, especially if the custom versions are designed for objects of a particular type. Class-specific allocators are an example application of fixed-size allocators such as those offered by Boost's Pool library. If your application is single-threaded, but your compilers' default memory management routines are thread-safe, you may be able to win measurable speed improvements by writing thread-unsafe allocators. Of course, before jumping to the conclusion thatoperator new andoperator delete are worth speeding up, be sure to profile your program to confirm that these functions are truly a bottleneck.

  • To reduce the space overhead of default memory management. General-purpose memory managers are often (though not always) not just slower than custom versions, they often use more memory, too. That's because they often incur some overhead for each allocated block. Allocators tuned for small objects (such as those in Boost's Pool library) essentially eliminate such overhead.

  • To compensate for suboptimal alignment in the default allocator. As I mentioned earlier, it's fastest to accessdoubles on the x86 architecture when they are eight-byte aligned. Alas, theoperator news that ship with some compilers don't guarantee eight-byte alignment for dynamic allocations ofdoubles. In such cases, replacing the defaultoperator new with one that guarantees eight-byte alignment could yield big increases in program performance.

  • To cluster related objects near one another. If you know that particular data structures are generally used together and you'd like to minimize the frequency of page faults when working on the data, it can make sense to create a separate heap for the data structures so they are clustered together on as few pages as possible. Placement versions ofnew anddelete (seeItem 52) can make it possible to achieve such clustering.

  • To obtain unconventional behavior. Sometimes you wantoperatorsnew anddelete to do something that the compiler-provided versions don't offer. For example, you might want to allocate and deallocate blocks in shared memory, but have only a C API through which to manage that memory. Writing custom versions ofnew and delete (probably placement versions — again, seeItem 52) would allow you to drape the C API in C++ clothing. As another example, you might write a customoperator delete that overwrites deallocated memory with zeros in order to increase the security of application data.

Implementation:

void * operator new(std::size_t size) throw(std::bad_alloc){                                      // your operator new might  using namespace std;                 // take additional params  if (size == 0) {                     // handle 0-byte requests    size = 1;                          // by treating them as  }                                    // 1-byte requests  while (true) {   attempt to allocate size bytes;    if (the allocation was successful)       return (a pointer to the memory);    // allocation was unsuccessful; find out what the    // current new-handling function is (see below)    new_handler globalHandler = set_new_handler(0);    set_new_handler(globalHandler);    if (globalHandler) (*globalHandler)();    else throw std::bad_alloc();  }}
Handle size ==0 by forwarding it to standard new;
void * Base::operator new(std::size_t size) throw(std::bad_alloc){  if (size != sizeof(Base))               // if size is "wrong,"     return ::operator new(size);         // have standard operator                                          // new handle the request  ...                                     // otherwise handle                                          // the request here}

class Base {                            // same as before, but nowpublic:                                 // operator delete is declared  static void * operator new(std::size_t size) throw(std::bad_alloc);  static void operator delete(void *rawMemory, std::size_t size) throw();  ...};void Base::operator delete(void *rawMemory, std::size_t size) throw(){  if (rawMemory == 0) return;           // check for null pointer  if (size != sizeof(Base)) {           // if size is "wrong,"     ::operator delete(rawMemory);      // have standard operator     return;                            // delete handle the request  }  deallocate the memory pointed to by rawMemory;  return;}

6. the runtime system looks for a version of operator delete that takesthe same number and types of extra arguments asoperator new, and, if it finds it, that's the one it calls.

 if an operator new with extra parameters isn't matched by an operator delete with the same extra parameters, nooperator delete will be called if a memory allocation by thenew needs to be undone. 

Placement delete is called only if an exception arises from a constructor call that's coupled to a call to a placementnew. Applyingdelete to a pointer (such as pw above) never yields a call to a placement version ofdelete.Never.

Incidentally, because member function names hide functions with the same names in outer scopes (seeItem 33), you need to be careful to avoid having class-specificnews hide other news (including the normal versions) that your clients expect. Similarly,operator news in derived classes hide both global and inherited versions ofoperator new:

C++ offers the following forms of operator new at global scope:

void* operator new(std::size_t) throw(std::bad_alloc);      // normal newvoid* operator new(std::size_t, void*) throw();             // placement newvoid* operator new(std::size_t,                             // nothrow new —                   const std::nothrow_t&) throw();          // see Item 49

If you declare any operator news in a class, you'll hide all these standard forms. Unless you mean to prevent class clients from using these forms, be sure to make them available in addition to any customoperator new forms you create. For each operator new you make available, of course, be sure to offer the correspondingoperator delete, too. If you want these functions to behave in the usual way, just have your class-specific versions call the global versions.

An easy way to do this is to create a base class containing all the normal forms ofnew anddelete:

class StandardNewDeleteForms {public:  // normal new/delete  static void* operator new(std::size_t size) throw(std::bad_alloc)  { return ::operator new(size); }  static void operator delete(void *pMemory) throw()  { ::operator delete(pMemory); }  // placement new/delete  static void* operator new(std::size_t size, void *ptr) throw()  { return ::operator new(size, ptr); }  static void operator delete(void *pMemory, void *ptr) throw()  { return ::operator delete(pMemory, ptr); }  // nothrow new/delete  static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()  { return ::operator new(size, nt); }  static void operator delete(void *pMemory, const std::nothrow_t&) throw()  { ::operator delete(pMemory); }};

Clients who want to augment the standard forms with custom forms can then just use inheritance andusing declarations (seeItem 33) to get the standard forms:

class Widget: public StandardNewDeleteForms {           // inherit std formspublic:   using StandardNewDeleteForms::operator new;          // make those   using StandardNewDeleteForms::operator delete;       // forms visible   static void* operator new(std::size_t size,          // add a custom                             std::ostream& logStream)   // placement new     throw(std::bad_alloc);   static void operator delete(void *pMemory,           // add the corres-                               std::ostream& logStream) // ponding place-    throw();                                            // ment delete  ...};