Professional C__ - Marc Gregoire [327]
The New-Expression and operator new
There are six different forms of the new-expression, each of which has a corresponding operator new. Earlier chapters in this book already show four new-expressions: new, new[], nothrow new, and nothrow new[]. The following list shows the corresponding four operator new forms from the void* operator new(size_t size); // For new void* operator new[](size_t size); // For new[] void* operator new(size_t size, const nothrow_t&) noexcept; // For nothrow new void* operator new[](size_t size, const nothrow_t&) noexcept;// For nothrow new[] There are two special new-expressions that do no allocation, but invoke the constructor on an existing piece of storage. These are called placement new operators (including both single and array forms). They allow you to construct an object in preexisting memory like this: void* ptr = allocateMemorySomehow(); SpreadsheetCell* cell = new (ptr) SpreadsheetCell(); This feature is a bit obscure, but it’s important to realize that it exists. It can come in handy if you want to implement memory pools such that you reuse memory without freeing it in between. The corresponding operator new forms look as follows: void* operator new(size_t size, void* p) noexcept; void* operator new[](size_t size, void* p) noexcept; The Delete-Expression and operator delete There are only two different forms of the delete-expression that you can call: delete, and delete[]; there are no nothrow or placement forms. However, there are all six forms of operator delete. Why the asymmetry? The four nothrow and placement forms are used only if an exception is thrown from a constructor. In that case, the operator delete is called that matches the operator new that was used to allocate the memory prior to the constructor call. However, if you delete a pointer normally, delete will call either operator delete or operator delete[] (never the nothrow or placement forms). Practically, this doesn’t really matter: The C++ standard says that throwing an exception from delete results in undefined behavior, which means delete should never throw an exception anyway, so the nothrow version of operator delete is superfluous; and placement delete should be a no-op, because the memory wasn’t allocated in placement operator new, so there’s nothing to free. Here are the prototypes for the operator delete forms: void operator delete(void* ptr) noexcept; void operator delete[](void* ptr) noexcept; void operator delete(void* ptr, const nothrow_t&) noexcept; void operator delete[](void* ptr, const nothrow_t&) noexcept; void operator delete(void* p, void*) noexcept; void operator delete[](void* p, void*) noexcept; Overloading operator new and operator delete You can actually replace the global operator new and operator delete routines if you want. These functions are called for every new-expression and delete-expression in the program, unless there are more specific routines in individual classes. However, to quote Bjarne Stroustrup, “. . . replacing the global operator new and operator delete is not for the fainthearted.” (The C++ Programming Language, Third Edition, Addison-Wesley, 1997). We don’t recommend it either! If you fail to heed our advice and decide to replace the global operator new, keep in mind that you cannot put any code in the operator that makes a call to new: this will cause an infinite loop. For example, you cannot write a message to the console with cout. A more useful technique is to overload operator new and operator delete for specific classes. These overloaded operators will be called only when you allocate and deallocate objects of that particular class. Here is an example of a class that overloads the four non-placement forms of operator new and operator delete: #include class MemoryDemo { public: MemoryDemo() {} virtual ~MemoryDemo() {} void* operator new(std::size_t size); void operator delete(void* ptr) noexcept; void* operator new[](std::size_t