Effective C++, 2E | Item 8: Adhere to convention when writing operator new and operator delete. Back to Item 7: Be prepared for out-of-memory conditions. Continue to Item 9: Avoid hiding the "normal" form of new. 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 exception. In addition, the C++ standard requires that 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 language.) That being the case, pseudocode for a non-member operator new looks like this: 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)(); elsethrow 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, anyway? 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 effective. 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 terminate. 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: class Base { public: static void * operator new(size_t size); ... }; class Derived: public Base // Derived doesn't declare { ... }; // operator new Derived *p = new Derived; // callsBase::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 this: void * Base::operator new(size_t size) { if (size != sizeof(Base)) // if size is "wrong," return ::operator new(size); // have standard operator // new handlethe 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 C++ standard works in mysterious ways, and one of those ways is to decree that all freestanding classes have nonzero size. By definition, 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 new[]. (This function is usually called "array new," because it's hard to figure out how to pronounce "operator new[]".) If you decide to write operator new[], 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 new[] 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 new[] 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 new[], see Item M8. So much for the conventions you need to follow when writing operator new (and operator new[]). For operator delete (and its array counterpart, operator delete[]), 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 done. Back to Item 7: Be prepared for out-of-memory conditions. Continue to Item 9: Avoid hiding the "normal" form of new.