When writing operator new and operator Delete, follow the regular rules
When you rewrite operator new by yourself (cla10 explains why you sometimes rewrite it), it is important that the behavior provided by the function should be consistent with the default operator new of the system. In practice, the correct return value must be obtained. When the available memory is insufficient, the error processing function must be called (see Article 7). The 0-byte memory request must be processed. In addition, we should avoid hiding the standard form of new accidentally, but this is the topic of Clause 9.
The return value is simple. If the memory allocation request is successful, a pointer to the memory is returned. If the request fails, a STD: bad_alloc type exception is thrown in accordance with Clause 7.
But it is not that simple. Because operator new actually tries to allocate memory more than once, it needs to call the error processing function after each failure, and expects the error processing function to find a way to release memory elsewhere. Operator new throws an exception only when the pointer to the error processing function is null.
In addition, the C ++ standard requires that operator new returns a valid pointer even when the request is allocated 0 bytes of memory. (In fact, this strange requirement does make C ++ easy to use elsewhere)
In this way, the pseudo code of operator new in the form of non-class members looks like the following:
Void * operator new (size_t size) // operator new may have other parameters
...{
If (size = 0)... {// when processing 0-byte requests,
Size = 1; // it is processed as a 1-byte request.
}
While (1 )...{
Allocate size bytes of memory;
If (allocated successfully)
Return (pointer to memory );
// The allocation fails. Find the processing function for the current error.
New_handler globalhandler = set_new_handler (0 );
Set_new_handler (globalhandler );
If (globalhandler) (* globalhandler )();
Else throw STD: bad_alloc ();
}
}
The trick to process a zero-byte request is to process the request as one byte. This seems strange, but simple, legal, and effective. How often will you encounter a zero-byte request?
You may wonder why the error handler is set to 0 in the above pseudo code and then immediately restored. This is because there is no way to directly obtain the pointer to the error handler function, so it must be found by calling set_new_handler. The solution is stupid but effective.
Article 7 mentions that operator new contains an infinite loop, and the code above clearly illustrates this -- while (1) will lead to an infinite loop. The only way out of the loop is to successfully allocate the memory or handle errors. The Handler completes one of the events described in clause 7: get more available memory; A New-handler (error handler) is installed; New-handler is removed; an STD: bad_alloc or its derived type exception is thrown; or an error is returned. Now I understand why New-handler must do one of these jobs. If you do not do this, the loop in operator new will not end.
Many people do not realize that operator new often inherits quilt classes. This may cause some complexity. In the above pseudo code, the function will allocate size bytes of memory (unless the size is 0 ). Size is very important because it is a parameter passed to the function. But most operator new (including the one in Clause 10) written for classes are designed only for specific classes, not for all classes, nor for all their subclasses. This means that for operator new of Class X, the internal behavior of the function is precise sizeof (x) when it comes to objects: Not big or small. However, due to inheritance, operator new in the base class may be called to allocate memory for a subclass object:
Class base ...{
Public:
Static void * operator new (size_t size );
...
};
Class derived: public base // The derived class does not declare operator new
...{...};//
Derived * P = new derived; // call base: Operator new
If the base-class operator new doesn't need to work hard to handle this situation-this is unlikely to happen-the simplest way is to transfer the memory allocation requests with this "error" quantity to the standard operator new to handle, as follows:
Void * base: Operator new (size_t size)
...{
If (size! = Sizeof (base) // if the number is "Incorrect", make the standard operator new
Return: Operator new (size); // process the request
//
... // Otherwise process this request
}
"Stop !" I heard you say, "You forgot to check a situation that is unreasonable but may happen-the size may be zero !" Yes, I didn't check it, but please don't say that again next time. :) But the check is actually done, but it is integrated into size! = Sizeof (base) statement. The C ++ standard is weird. One of them is that the size of the independent (freestanding) class is non-zero. Therefore, sizeof (base) is never zero (even if the base class has no members). If the size is zero, the request will go to: Operator new, it is used to process requests in a reasonable way. (Interestingly, if the base is not an independent class, sizeof (base) may be zero. For details, see "my article on counting objects ").
To control the memory allocation of class-based arrays, you must implement the array form of operator New-operator new [] (this function is often called "array new ", because I cannot figure out how to pronounce "operator new ). When writing operator new [], remember that you are facing the "original" memory and cannot perform any operation on objects that do not exist in the group. In fact, you do not even know the number of objects in the array, because you do not know the size of each object. Operator new [] of the base class is used to allocate memory to the array of the subclass object through inheritance, and the subclass object is usually larger than the base class. Therefore, it cannot be assumed that the size of each object in base: Operator new [] is sizeof (base). That is to say, the number of objects in the array is not necessarily (number of request bytes) /sizeof (base ). For a detailed introduction to operator new [], refer to the Terms and Conditions M8.
All the conventions to be followed when operator new (and operator new []) is rewritten. Operator Delete (and its partner operator Delete []) is simpler. All you need to remember is that C ++ ensures that deleting null pointers is always safe, so you must fully apply this guarantee. The following is a pseudo code of operator Delete, which is a non-class member:
Void operator Delete (void * rawmemory)
...{
If (rawmemory = 0) return; file: // If/if the pointer is null, return
//
Releases the memory pointed to by rawmemory;
Return;
}
The class member version of this function is also simple, but the size of the deleted object must be checked. Assume that operator new of the class transfers the allocation request of "error" size to: Operator new, the deletion request of "error" size must also be transferred to: Operator delete:
Class base... {// is the same as above, but it is declared here
Public: // operator Delete
Static void * operator new (size_t size );
Static void operator Delete (void * rawmemory, size_t size );
...
};
Void base: Operator Delete (void * rawmemory, size_t size)
...{
If (rawmemory = 0) return; // check the NULL pointer
If (size! = Sizeof (base)... {// If the size is "Incorrect ",
: Operator Delete (rawmemory); // Let the standard operator handle the request
Return;
}
Releases the memory directed to rawmemory;
Return;
}
It can be seen that the operator new and operator Delete (and their array form) Regulations are not so troublesome, and it is important to comply with them. As long as the memory allocation program supports the new-handler function and correctly processes the zero-memory request, it is almost the same. If the memory release program processes a null pointer, there is nothing else to do. Adding inheritance support to functions of the class member version will soon be completed.