Item 8: Adhere to convention when writing operator
new
and operator
delete
.
When you take it upon yourself to write operator
new
(Item 10 explains why you might want to), it's important that your function(s) offer behavior that is consistent with the default operator
new
. In practical terms, this means having the right return value, calling an error-handling function when insufficient memory is available (see Item 7), and being prepared to cope with requests for no memory. You also need to avoid inadvertently hiding the "normal" form of new
, but that's a topic for Item 9.
The return value part is easy. If you can supply the requested memory, you just return a pointer to it. If you can't, you follow the rule described in Item 7 and throw an exception of type std::bad_alloc
.
It's not quite that simple, however, because operator
new
actually tries to allocate memory more than once, calling the error-handling function after each failure, the assumption being that the error-handling function might be able to do something to free up some memory. Only when the pointer to the error-handling function is null does operator
new
throw an
In addition, the operator
new
return a legitimate pointer even when 0 bytes are requested. (Believe it or not, requiring this odd-sounding behavior actually simplifies things elsewhere in the
That being the case, pseudocode for a non-member operator
new
looks like
void * operator new(size_t size) // your operator new might { // take additional params
if (size == 0) { // handle 0-byte requests size = 1; // by treating them as } // 1-byte requests
while (1) {
attempt to allocate size
bytes;
if (the allocation was successful) return (a pointer to the memory);
// allocation was unsuccessful; find out what the // current error-handling function is (see Item 7) new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler);
if (globalHandler) (*globalHandler)(); else throw std::bad_alloc(); } }
The trick of treating requests for zero bytes as if they were really requests for one byte looks slimy, but it's simple, it's legal, it works, and how often do you expect to be asked for zero bytes,
You may also look askance at the place in the pseudocode where the error-handling function pointer is set to null, then promptly reset to what it was originally. Unfortunately, there is no way to get at the error-handling function pointer directly, so you have to call set_new_handler
to find out what it is. Crude, yes, but also
Item 7 remarks that operator
new
contains an infinite loop, and the code above shows that loop explicitly while
(1)
is about as infinite as it gets. The only way out of the loop is for memory to be successfully allocated or for the new-handling function to do one of the things described in Item 7: make more memory available, install a different new-handler, deinstall the new-handler, throw an exception of or derived from std::bad_alloc
, or fail to return. It should now be clear why the new-handler must do one of those things. If it doesn't, the loop inside operator
new
will never
One of the things many people don't realize about operator
new
is that it's inherited by subclasses. That can lead to some interesting complications. In the pseudocode for operator
new
above, notice that the function tries to allocate size
bytes (unless size
is 0). That makes perfect sense, because that's the argument that was passed to the function. However, most class-specific versions of operator
new
(including the one you'll find in Item 10) are designed for a specific class, not for a class or any of its subclasses. That is, given an operator
new
for a class X
, the behavior of that function is almost always carefully tuned for objects of size sizeof(X)
nothing larger and nothing smaller. Because of inheritance, however, it is possible that the operator
new
in a base class will be called to allocate memory for an object of a derived
class Base { public: static void * operator new(size_t size); ... };
class Derived: public Base // Derived doesn't declare { ... }; // operator new
Derived *p = new Derived; // calls Base::operator new!
If Base
's class-specific operator
new
wasn't designed to cope with this and chances are slim that it was the best way for it to handle the situation is to slough off calls requesting the "wrong" amount of memory to the standard operator
new
, like
void * Base::operator new(size_t size) { if (size != sizeof(Base)) // if size is "wrong," return ::operator new(size); // have standard operator // new handle the request
... // otherwise handle // the request here }
"Hold on!" I hear you cry, "You forgot to check for the pathological-but-nevertheless-possible case where size
is zero!" Actually, I didn't, and please stop using hyphens when you cry out. The test is still there, it's just been incorporated into the test of size
against sizeof(Base)
. The sizeof(Base)
can never be zero (even if it has no members), so if size
is zero, the request will be forwarded to ::operator
new
, and it will become that function's responsibility to treat the request in a reasonable fashion. (Interestingly, sizeof(Base)
may be zero if Base
is not a freestanding class. For details, consult my article on counting objects.)
If you'd like to control memory allocation for arrays on a per-class basis, you need to implement operator
new
's array-specific cousin, operator
. (This function is usually called "array new," because it's hard to figure out how to pronounce "operator operator
, remember that all you're doing is allocating raw memory you can't do anything to the as-yet-nonexistent objects in the array. In fact, you can't even figure out how many objects will be in the array, because you don't know how big each object is. After all, a base class's operator
might, through inheritance, be called to allocate memory for an array of derived class objects, and derived class objects are usually bigger than base class objects. Hence, you can't assume inside Base::operator
that the size of each object going into the array is sizeof(Base)
, and that means you can't assume that the number of objects in the array is (
bytes
requested
)/sizeof(Base)
. For more information on operator
, see Item M8.
So much for the conventions you need to follow when writing operator
new
(and operator
). For operator
delete
(and its array counterpart, operator
), things are simpler. About all you need to remember is that C++ guarantees it's always safe to delete the null pointer, so you need to honor that guarantee. Here's pseudocode for a non-member operator delete
:
void operator delete(void *rawMemory) { if (rawMemory == 0) return; // do nothing if the null // pointer is being deleted
deallocate the memory pointed to by rawMemory;
return; }
The member version of this function is simple, too, except you've got to be sure to check the size of what's being deleted. Assuming your class-specific operator
new
forwards requests of the "wrong" size to ::operator
new
, you've got to forward "wrongly sized" deletion requests to ::operator
delete
:
class Base { // same as before, but now public: // op. delete is declared 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 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; }
The conventions, then, for operator
new
and operator
delete
(and their array counterparts) are not particularly onerous, but it is important that you obey them. If your allocation routines support new-handler functions and correctly deal with zero-sized requests, you're all but finished, and if your deallocation routines cope with null pointers, there's little more to do. Add support for inheritance in member versions of the functions, and presto! you're