📄 ei7.htm
字号:
return memory;}If the duplicated calls to std::set_new_handler caught your eye, turn to Item M9 for information on how to eliminate them.Clients of class X use its new-handling capabilities like this: void noMoreMemory(); // decl. of function to // call if memory allocation // for X objects failsX::set_new_handler(noMoreMemory); // set noMoreMemory as X's // new-handling functionX *px1 = new X; // if memory allocation // fails, call noMoreMemorystring *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 need.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 it: template<class T> // "mixin-style" base classclass NewHandlerSupport { // for class-specificpublic: // 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 0template<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 newClients 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.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 C++ standardization committee didn't want to abandon the established test-for-0 code base, so they provided alternative forms of operator new (and operator new[] 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 used: class Widget { ... };Widget *pw1 = new Widget; // throws std::bad_alloc if // allocation failsif (pw1 == 0) ... // this test must failWidget *pw2 = new (nothrow) Widget; // returns 0 if allocation // failsif (pw2 == 0) ... // this test may succeedRegardless 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 forms. Back to Item 6: Use delete on pointer members in destructors.Continue to Item 8: Adhere to convention when writing operator new and operator delete.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -