Article 49: Understanding the behavior of new-handler what happens to the new exception?
In the old compiler, a null pointer is returned when operator new allocates memory for failure. And now it throws an exception.
Before this exception is thrown, a client-specified error handler is called First: the so-called New-handler:set_new_handler function. there are declarations in the standard library:
namespace std{ typedef void (*new_handler)(); // 函数指针,返回为void,参数为空 new_handler set_new_handler(new_handler p) throw();}
The Set_new_handler parameter is a function pointer to a function that is called when enough memory cannot be allocated. The return value is also a function pointer to the New-handler function before the function call.
Now let's specify a sample execution code after failing to allocate memory as an example:
void OutOfMem() // 返回型为void,参数为空{ std::cerr << "Unable to satisfy request for memory" << std::endl; std::abort();}
Then in the main function:
int main(){ std::set_new_handler(OutOfMem); // 相当于注册一个失败之后的执行函数 int *pBigData = new int[100000000L]; return 0;}
In this case, if the allocation fails, then OUTOFMEM is called and the Abort function is executed.
In fact, a good new-handler function should be constantly called, and internal processing frees up some memory, and then tries to possibly be able to allocate memory successfully and so on. The author summarizes the characteristics of a series of well-designed New-handler functions:
- allow more memory to be used. implementation of a strategy: initially allocated a large memory, the first time the New-handler is called back to the program.
- install another new-handler. If you know that another new-handler can get enough memory, then install it.
- Remove the New-handler. If you pass a null pointer to Set_new_handler, the allocation failure throws an exception directly.
- does not return. usually call exit or abort.
Now let's write a custom version of new based on the Widget class.
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;};
Looking at the code above, first you may have a question: Why declare it as a static function?
Think about the characteristics of static, what if we don't declare my static? That is to say, these functions will become member functions of the widget, that is, these functions will only exist if the widget is constructed successfully. But our new is to construct this widget, how can we call a function that doesn't exist yet?
So we're going to declare it as a static function that calls these static functions to construct the Widget object.
Let's look at the implementation of these static member functions.
std::new_handler Widget::currentHandler = 0;
The current error handling function is initialized to NULL, which means that the exception is thrown directly and not processed.
std::new_handler Widget::set_new_handler(std::new_handler p) throw(){ std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler;}
Do you remember what we said at the beginning of this article? The Set_new_handler function is to set the incoming error handler to the current error handler and return an old one. The code above is a standard version of the Set_new_handler.
Now our focus is on the operator new function, and we're actually just doing a layer of encapsulation on global new, making it our own custom new. Simply put, register a proprietary error handler for the Widget class, and then call the global operator new function, but register it after it ends or throws an exception. The key is to register back, here using the Raii method of clause 13 to manage: Once the destructor is registered back.
class NewHandlerHolder{public: explicit NewHandlerHolder(std::new_hander nh) : handler(nh){} ~NewHandlerHolder() { std::set_new_handler(handler); }private: std::new_handler handler; // 阻止copying NewHandlerHolde(const NewHandlerHolder &); NewHandlerHolde &operator=(const NewHandlerHolder&);};
OK, now we can confidently write our custom new without worrying about memory leaks.
void *Widget::operator new(std::size_t size) throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_handler(currentHandler)); return ::new(size);}
The first statement of this function does two things:
(1) Set the error function to Currenthandler when new fails.
(2) Set_new_handler returns the original error handling function, which is passed in as a constructor parameter, saved, and then set back.
Now let's look at some of the possible invocation methods in the main function:
void OutOfMem(); // 错误处理函数Widget::set_new_handler(OutOfMem); // 设置为Widget的专属错误处理函数Widget *pw1 = new Widget; // 内存分配失败会调用OutOfMem函数
The end of this article also describes the use of Nothrow: If we want to return a null pointer when the allocation fails, instead of throwing an exception, you can:
Widget *pw2 = new(std::nothrow) Widget;
Author summary
Set_new_handler allows a client to specify a function that is called when the memory allocation is not met.
Nothrow New is a fairly limited tool because it only applies to memory allocations, and subsequent constructor calls may throw exceptions.
Article 50: Understanding the appropriate replacement time for new and delete
This article on the comparison of the text on the things, I think it is necessary to take into account in the development of various situations in order to fully understand. Here are just a few of the key points to illustrate:
- Used to detect errors in use.
- to enhance performance. the new and delete used by the compiler are too moderate, it must take into account the size of the memory allocation, consider the fragmentation problem, etc., when we need more efficient time can not consider these. It would be better to use a custom version.
- In order to collect statistical data on use.
- In order to detect the use of errors.
- in order to increase the speed of distribution and restitution. For example, the compiler provides a thread-safe version, and we are single-threaded, and there is no insecure situation. Self-customization is more efficient.
- to reduce the extra overhead of the default memory manager. The default space overhead is greater, and it requires an additional token.
- In order to cluster the related objects.
- In order to obtain non-traditional behavior.
Author summary
There are a number of reasons to write a custom new and delete, including improvements to performance, debugging of heap errors, and mobile heap usage information.
Article 51: Need to stick to general new when writing new and delete to return the correct value
The return value of operator new should be simple: if memory is allocated successfully, a pointer is returned to it. If it fails, it throws a Bad_alloc exception.
C + + stipulates that if a customer wants a 0byte, it also returns a legitimate pointer. So the 0 to be special treatment. A pseudo-code is provided below:
void* operator new(std::size_t size){ if(size == 0) { size = 1; } while(true) { // 尝试分配size bytes if(分配成功) { return (一个指向分配内存的指针); } // 分配失败,找出目前的错误处理函数并处理 new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if(globalHandler) { (*globalHandler)(); } else { throw std::bad_alloc(); } }}
The puzzling part of the above is probably why we first set the error handler to an empty one and then set it back. just to take out the error handler and execute it.
For a while (true), the next clause will be discussed in detail.
New under the Inheritance system
Look at the code below and you'll see clearly the error in the inheritance system, new:
class Base{public: static void *operator new(std::size_t size) throw(std::bad_alloc); ...};class Derive : public Base{public: ....};
Immediately following execution:
Derive *p = new Derive; // 这里调用的是Base的new!
Obviously, if derive does not declare its own custom new, it will invoke the inherited base's new function. Generally, the memory sizes of base and derive are not equal, so calls will be faulted.
If derive is not customized and wants to use global, it should be changed to this:
void *Base::operator(std::size_t size) throw(std::bad_alloc){ if(size != sizeof(Base)) { return ::operator(size)); } ... // 这里是处理Base类的new}
The writing of Delete is noted
The delete is relatively simple. But let's make sure that delete a null is also valid. In addition, it should be written under the inheritance system:
void operator delete(void *rawMemory,std::size_t size) throw(){ if(rawMemory = null) return ; if(size != sizeof(Base)) { ::operator delete(rawMemory); return ; } // 归还内存等操作}
Author summary
Operator new should contain an infinite loop in which to attempt to allocate memory, and if it does not meet the memory requirements, it should call New-handler. It should also be able to handle 0bytes applications. The class-specific version of Ze should also handle "larger (error) applications than the correct size".
operator delete should not do anything when a null pointer is received. The class-specific version should also handle "larger (error) applications than the correct size."
Clause 52: Write placement new also to write placement delete what is called placement
Simply put, in operator new or delete functions, there are other parameters that need to be in addition to the parameters that must be required, such a operator placement version.
As our typical version of new and delete is:
void *operator new(std::size_t size);void *operator delete(void* rawMemory) throw(); //global作用域的典型声明void *operator delete(void* rawMemory,std::size_t size); // class作用域的典型声明
For example, in class, it is an extra parameter that does not belong to void* and size_t.
The placement version is a way to prevent memory leaks.
Consider the following code:
Widget *pw = new Widget;
A default constructor for new and a widget is called here. assuming the first function call succeeds, memory is allocated. The second function fails, then PW has not been assigned a value, the client is not able to return memory, it will cause a memory leak!
This cancels the memory allocation and the task of replying to had cleared falls on the C + + run time system. Now there is a problem, how do you know how to call the corresponding delete version of the runtime? If the wrong delete version is called, the expected result will not be obtained.
Let the runtime system find the correct delete version
When we have the placement version of new, if the constructor throws an exception, the runtime system first looks for a operator delete with the "(extra) number of parameters and the same type as operator new". If the call is found, if not, then no delete function will be called!!!
If you do not call Delete, the memory will leak in this case, so we must define the corresponding operator delete for our placement new declaration:
class Widget{public: static void *operator new(std::size_t size,std::ostream &logStream) throw(std::bad_allc); static void operator delete(void *pMemory) throw(); static void operator delete(void *pMemory,std::ostream& logStream) throw();};
Like this, the last delete function is a corresponding placement delete function, which has an additional parameter of LogStream.
Now let's look at the call:
Widget *pw = new(std::cerr) Widget;delete pw;
This time we ensure that the memory is not compromised:
(1) New succeeds and the constructor succeeds, so delete pw deletes the correct memory.
(2) New succeeds but the constructor fails, then delete calls the placement delete version, and the runtime system will handle it correctly.
Author summary
When you write a placement operator new, make sure that the corresponding placement operator delete is also written. If you do not do this, your program may have a memory leak that is hidden and is intermittent.
When you declare placement new and placement Delete, be sure not to inadvertently (unintentionally) obscure their normal version.
Viii. Customizing New and delete