Effective C++ 筆記 —— Item 51: Adhere to convention when writing new and delete.
The return value part of operator new is easy. If you can supply the requested memory, you return a pointer to it. If you can't, you follow the rule described in Item 49 and throw an exception of type bad_alloc.
It's not quite that simple, however, because operator new actually tries to allocate memory more than once, calling the new-handling function after each failure. The assumption here is that the new-handling function might be able to do something to free up some memory. Only when the pointer to the new-handling function is null does operator new throw an exception.
C++ requires that operator new return a legitimate pointer even when zero bytes are requested.
Many people don‘t realize that operator new member functions are inherited by derived classes.
one of the most common reasons for writing a custom memory manager is to optimize allocation for objects of a specific class, not for a class or any of its derived classes. That is, given an operator new for a class X, the behavior of that function is typically 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(std::size_t size) throw(std::bad_alloc); // ... }; class Derived : public Base // Derived doesn’t declare operator new { // ... };
If Base’s class-specific operator new wasn’t designed to cope with this — and chances are that it wasn't — 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(std::size_t size) throw(std::bad_alloc) { if (size != sizeof(Base)) return ::operator new(size); // if size is "wrong," have standard operator new handle the request // otherwise handle the request here // ... }
If you decide to write operator new[], remember that all you're doing is allocating a chunk of 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. First, 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). Second, the size_t parameter passed to operator new[] may be for more memory than will be filled with objects, because, as Item 16 explains, dynamically allocated arrays may include extra space to store the number of array elements.
For 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.
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 { public: // same as before, but now operator delete is declared static void* operator new(std::size_t size) throw(std::bad_alloc); static void operator delete(void *rawMemory, std::size_t size) throw(); // ... }; void Base::operator delete(void *rawMemory, std::size_t size) throw() { if (rawMemory == 0) return; // check for null pointer if size is "wrong," if (size != sizeof(Base)) { ::operator delete(rawMemory); // have standard operator delete handle the request deallocate the memory pointed to by rawMemory; return; } return; }
Things to Remember
- operator new should contain an infinite loop trying to allocate memory, should call the new-handler if it can't satisfy a memory request, and should handle requests for zero bytes. Class-specific versions should handle requests for larger blocks than expected.
- operator delete should do nothing if passed a pointer that is null. Class-specific versions should handle blocks that are larger than expected.