C ++ proverbs: Understanding new-handler's behavior

Source: Internet
Author: User
C ++ proverbs: understand the new-handler behavior-general Linux technology-Linux programming and kernel information. The following is a detailed description. When operator new cannot meet a memory allocation request, it throws an exception ). A long time ago, he returned a null pointer (null pointer), while some older compilers are still doing this. You can still achieve your previous goals (to some extent), but I will discuss it at the end of this article.

Before operator new throws an exception in response to a memory request that cannot be met, it first calls an error-handling function called new-handler that can be specified by the customer ). (This is not completely accurate. operator new is slightly more complicated than this. Details will be discussed in the next article .) To specify the out-of-memory-handling function, the customer calls set_new_handler-a standard library function declared in:

Namespace std {
Typedef void (* new_handler )();
New_handler set_new_handler (new_handler p) throw ();
}

As you can see, new_handler is a pointer typedef, which points to a function that does not get or return anything, And set_new_handler is a function that gets and returns a new_handler. (The "throw ()" at the end of the set_new_handler Declaration is an exception specification (exception specification ). It basically means that this function will not throw any exceptions, although the truth is more interesting. For details, see C ++ proverbs: code for exception security.)

The shape parameter of set_new_handler is a pointer to the function, which should be called when operator new cannot allocate the requested memory. The return value of set_new_handler is a pointer to the function. This function is valid before set_new_handler is called.

You can use set_new_handler as follows:

// 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 [distinct L];
...
}

If operator new cannot allocate space for 100,000,000 integers, outOfMem will be called and the program will stop sending an error message. (By The Way, consider what will happen if the memory must be dynamically allocated when the error message is written to cerr .)

When operator new cannot meet a memory request, it repeatedly calls the new-handler function until it can find enough memory. However, this high-level description is enough to export a well-designed new-handler function, which must do one of the following:

· Make more memory available (to Make more memory available ). This may make the next memory allocation attempt of operator new successful. One way to implement this policy is to allocate a large block of memory when the program starts, and then release it for the program when new-handler is called for the first time.

· Install a different new-handler (Install a different new-handler ). If the current new-handler cannot make more memory available, maybe it knows that there is a different new-handler that can do it. If so, the current new-handler can install another new-handler in its own location (by calling set_new_handler ). When operator new calls the new-handler function next time, it will get the recently installed one. (A change on this main line is to let a new-handler change its own behavior, so that the next time it is called, you can do something different. One way to do this is to make new-handler change the static (static), namespace-specific (namespace-specific), or global (global) actions that affect new-handler behaviors) .)

· Deinstall the new-handler (uninstall new-handler), that is, pass the NULL pointer to set_new_handler. No new-handler is installed. When the memory allocation fails, operator new throws an exception.

· Throw an exception (throwing an exception). The type is bad_alloc or other types inherited from bad_alloc. Such exceptions will not be caught by operator new, so they will be propagated to the place where memory requests are sent.

· Not return (no return is returned). In typical cases, call abort or exit.

These options enable great flexibility when implementing new-handler functions.

Sometimes you may want to use different methods to handle memory allocation failures based on different objects to be 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 ++ does not support class-specific new-handlers, but it does not. You can implement this line by yourself. You only need to let every class provide its own version of set_new_handler and operator new. Class set_new_handler allows the customer to specify new-handler for this class (just as standard set_new_handler allows the customer to specify global new-handler ). Operator new of class ensures that class-specific new-handler is used instead of global new-handler when memory is allocated to class objects.

Suppose you want to handle memory allocation failure for the Widget class. You must be clear about the function called when operator new cannot allocate enough memory for a Widget object, so you need to declare a static member (static member) of the new_handler type) point to the new-handler function of this class. Widgets look 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 (static class members) must be defined outside the class definition (unless they are const and integral), so:

Std: new_handler Widget: currentHandler = 0; // init to null in the class
// Impl. file

The set_new_handler function in the Widget saves any pointer passed to it and returns any pointer saved during the previous call. This is exactly what set_new_handler's Standard Edition does:

Std: new_handler Widget: set_new_handler (std: new_handler p) throw ()
{
Std: new_handler oldHandler = currentHandler;
CurrentHandler = p;
Return oldHandler;
}


In the end, operator new of the Widget will do the following:

Call standard set_new_handler with the error-handling function of the Widget as the parameter. In this way, the new-handler of the Widget is installed as global new-handler.

Call global operator new for real memory allocation. If the allocation fails, global operator new calls the new-handler of the Widget because the function was just installed as global new-handler. If global operator new still cannot allocate memory, it will throw a bad_alloc exception. In this case, the operator new of the Widget must restore the original global new-handler and then spread the exception. To ensure that the original new-handler is always restored, Widgets treat global new-handler as a resource and follow the suggestions in C ++ proverbs: using objects to manage resources, use resource-management objects to prevent resource leaks ).

If global operator new can allocate enough memory for a Widget object, operator new of the Widget returns a pointer to the allocated memory. The destructor (destructor) of the object used to manage global new-handler automatically restores global new-handler to the status before calling the operator new of the Widget.

The following describes how to express all these things in C ++. We start with resource-handling class. In addition to basic RAII operations (resources are obtained during the construction process and released during the analysis process) (C ++ proverbs: use object to manage resources), nothing more:

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
Operator = (const NewHandlerHolder &);
};

This makes the implementation of operator new of the Widget very simple:

Void * Widget: operator new (std: size_t size) throw (std: bad_alloc)
{
NewHandlerHolder // install the Widget's
H (std: set_new_handler (currentHandler); // new-handler

Return: operator new (size); // allocate memory
// Or throw

} // Restore global
// New-handler

The customer of a Widget uses its new-handling capabilities (processing new capabilities) as follows ):

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
// Nothing (I. e., null)

Widget * pw2 = new Widget; // if mem. alloc. fails, throw
// Exception immediately. (There is
// No new-handling function
// Class Widget .)

No matter what the class is, the code for implementing this scheme is the same, so reusing it elsewhere is a reasonable goal. One simple way to make it possible is to create a "mixin-style" base class ("mixed style" base class), that is, a design that allows derived classes (derived class) A base class that inherits a single specific capability (in the current situation, it is the ability to set a class-specific new-handler ). Then convert the base class (base class) into a template, so that you can get different copies of the class data for each inheriting class (inherited class.

The base class (base class) part of this design allows derived classes (derived class) to inherit the set_new_handler and operator new functions required by all of them, and this design template (template) make sure that each inheriting class (inherited class) has a different currentHandler data member (data member ). This may sound complicated, but the code looks reliable and familiar. In fact, the only difference is that it can now be used on any class that needs it:

Template // "mixin-style" base class
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
Private:
Static std: new_handler currentHandler;
};

Template
Std: new_handler
NewHandlerSupport: set_new_handler (std: new_handler p) throw ()
{
Std: new_handler oldHandler = currentHandler;
CurrentHandler = p;
Return oldHandler;
}

Template
Void * NewHandlerSupport: 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
Std: new_handler NewHandlerSupport: currentHandler = 0;

With this class template (class template), it is easy to add set_new_handler support for widgets: widgets only need to be inherited from NewHandlerSupport. (It may look strange, but I will explain more details below .)

Class Widget: public NewHandlerSupport {
... // As before, but without declarations
}; // Set_new_handler or operator new

These are all widgets need to do to provide a class-specific set_new_handler.

However, you may still be worried about inheriting widgets from NewHandlerSupport. If so, when you notice that the NewHandlerSupport template never uses its type parameter T, you may be more worried. It does not need to do that. All we need is to provide a copy of different NewHandlerSupport-especially its static data member (static data member) currentHandler-for each class inherited from NewHandlerSupport. Template parameter T is only used to distinguish an inheriting class from another one. The template mechanism automatically generates a copy of currentHandler for T in each instantiated NewHandlerSupport.

Widgets inherit from a templatized base class (templated base class) that treats widgets as a type parameter (type parameter). If this concept makes you confused, you don't have to worry about it. It initially has this impact on everyone. However, it evolved into a useful technology with a name, although it does not seem to reflect the fact that they saw it for the first time. It is called curiously recurring template pattern (the unique recursive template pattern) (CRTP ). True.

In this regard, I have published an article suggesting a better name: "Do It For Me", because when a Widget inherits from NewHandlerSupport, It actually says: "I am a Widget, And I want to inherit from the NewHandlerSupport class for the Widget." No one uses my proposed name (or even myself), but one way to consider CRTP as "do it for me" may help you understand templatized inheritance (templated inheritance) what are you doing.

Templates such as NewHandlerSupport make it easy to add a class-specific new-handler for any class in need. However, mixin-style inheritance always leads to the topic of multiple inheritance (Multi-inheritance), and before we go down this path, you need to read C ++ proverbs: exercise caution when using multi-inheritance.

Until 1993, C ++ required operator new not to allocate the requested memory to return null. Operator new is now specified to throw a bad_alloc exception, but many C ++ programs are written before the compiler starts to support this revision standard. The C ++ Standardization Committee does not want to discard the code Basics of the test-for-null (check whether it is null), so they provide an alternative form of operator new, it is used to provide the traditional act of failure-yields-null (failure leads to null. These forms are called the "nothrow" form, which is partly because they use the nothrow objects (defined in the header file) 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
// The Widget fails

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

For exceptions, nothrow new provides less mandatory assurance than originally looked. In the expression "new (std: nothrow) Widget", two things occur. First, operator new's nothrow version is called to allocate enough memory for a Widget object. If the allocation fails, operator new returns null pointer. However, if it succeeds, the Widget constructor is called, and at this moment, all the gambling bets are invalid. Widget constructor can do anything it wants. It may have new memory, but if it does, it is not forced to use nothrow new. So, although operator new called in "new (std: nothrow) Widget" won't be thrown, Widget constructor can. If it does, the exception is propagated as usual. Conclusion? The use of nothrow new can only ensure that operator new will not throw, but cannot guarantee that an expression like "new (std: nothrow) Widget" will never cause an exception. Among all possibilities, you 'd better never need nothrow new.

Whether you are using "normal" (that is, exception-throwing) new or a little short, it is very important to understand the behavior of new-handler, because it can be used in two forms.

Things to Remember

· Set _ new_handler allows you to specify a function that can be called when the memory allocation request cannot be satisfied.

· Nothrow new has limited function, because it is only applicable to memory allocation, and subsequent constructor calls may still throw exceptions.
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.