A
When we write down this statement:
widget* pw = new Widget;
At this time, a total of two functions are called: one allocates memory operator new, the other is the widget's default constructor.
Suppose the first call succeeds, and the second throws an exception. The memory allocated by step one must be canceled and restored had cleared, otherwise it will cause a memory leak. At this point, the customer is not able to return the memory, because the widget constructor throws an exception, PW has not been assigned a value, the client has no pointer to the returned memory.
This time, cancel step one, and restore the responsibility of had cleared to the C + + operating system.
The runtime system will gladly invoke the corresponding operator delete version of operator New called Step One, provided that he has to know which operator delete is called (there may be many).
If you're dealing with new and delete with a normal signature, it's not a problem,
Normal new and corresponding to the normal delete:
void* operator New (std::size_t) throw (std::bad_alloc); void operator delete (void* rawmemory) throw ();//global Normal signature in scope void operator delete (void* rawmemory, std::size_t size) throw (), typical signature in//class scope.
Two
But when you start declaring an abnormal form of operator new, that is, operator new with additional parameters.
At this point, the question of "which delete accompanies this new" emerges:
For example:
Class-specific operator new requires a ostream, a log of relevant distribution information, and a normal form of class-specific operator delete:
Class widget{public: static void* operator new (std::size_t size, std::ostream& logstream) throw (std::bad_alloc ); Static void* operator Delete (void* pmemory, std::size_t size) throw (); };
If operator new accepts parameters other than certain size_t, this is called the placement new.
One of the many placement new is "accept a pointer to the place where the object is constructed", and operator new looks like this:
void* operator New (std::size_t, void* pmemory) throw ();
widget* pw = new (Std::cerr) Widget;
In this case, if the widget constructor throws an exception, the runtime system cannot know how the really called New operation works, so the had cleared cannot be deallocated and restored. The run-time system looks for "a operator delete with the same number and type of parameters as operator new." Should be:
void operator Delete (void*, std::ostream&) throw ();
is called placement deletes.
Now, since the widget does not declare the placement version of operator delete, the runtime system does nothing. If the widget constructor throws an exception, no operator delete is called.
So it causes a memory leak.
Three
The rule is simple: if a operator new with an extra parameter does not have a corresponding version operator delete with the same extra parameter, then when new's memory allocation action needs to be canceled and restored, no operator delete is called. It is therefore necessary to declare a placement delete, which corresponds to the placement new that has the memory function:
Class widget{public: static void* operator new (std::size_t size, std::ostream& logstream) throw (std::bad_alloc ); Static void* operator Delete (void* pmemory, std::size_t size) throw (); Static void* operator Delete (void* pmemory, std::ostream& logstream) throw (); };
After this change, if the widget constructor throws an exception:
widget* pw = new (Std::cerr) Widget;
The corresponding placement delete is automatically called, allowing the widget to ensure that the memory is not compromised.
This statement:
Delete pw;//calls the normal operator delete
Placement Delete is called only if there is an exception to the constructor triggered by the placement new call. Execution of a delete on a pointer will never cause a call to placement delete.
This means that for all placement new we must provide both a normal delete and a placement version.
Four
Because the name of the member function obscures the same name in its perimeter scope, you must be careful to have the class-specific news cover the other news (including the normal version) that the customer expects.
For example , if you have a base class that declares the only one placement operator new, customers will find that they cannot use the normal form of new:
Class Base {public:...static void* operator new (size_t size, ostream& logstream) throw (bad_alloc); This new will obscure the normal global form}; base* PB = new Base; Error! Because the normal form of operator new is obscured base* PB = new (Std::cerr) Base; Correct, call base placement new.
In the same vein, the operator news in derived classes obscures the global version and the inherited operator new version:
Class Derived:public Base { //inherit previous basepublic:...static void* operator new (std::size_t size) throw (Std::bad_alloc) ; Re-declare the normal form of new ...};D erived* pd = new (Std::clog) Derived; Error, because base's placement is obscured. derived* PD = new Derived; No problem, call derived's operator new.
Five
By default, C + + provides the following form of operator new within the global scope:
void* operator New (std::size_t) throw (std::bad_alloc); Normal new void* operator new (std::size_t, void*) throw (); Placement new void* operator new (std::size_t, const std::nothrow_t&) throw ();//Nothrow New
If you declare any operator news within class, it will obscure these standard forms. Unless you mean to prevent class customers from using these forms, make sure they are available outside of any custom operator new you generate.
One simple way to solve all the problems is to create a base class that contains all the normal forms of new and delete:
Class standardnewdeleteforms{public: //normal new/delete static void* operator new (std::size_t size) throw ( Std::bad_alloc) {return:: operator new (size);} static void operator Delete (void* pmemory) throw () {:: operator delete (pmemory);} Placement New/delete static void* operator new (std::size_t size, void* ptr) throw () {return:: operator New (size, PTR);} static void operator Delete (void* pmemory, void* ptr) throw () {return:: operator delete (pmemory, PTR);} Nothrow new/delete static void* operator new (std::size_t size, const std::nothrow_t& NT) throw () { Return:: operator new (size, NT);} static void operator Delete (void* pmemory, const std::nothrow_t&) throw () {:: operator delete (pmemory);}};
Customers who want to expand the standard form of a custom form can use the inheritance mechanism and the using declarative form to obtain the standard forms:
Class Widget:public standardnewdeleteforms{public: using standardnewdeleteforms::operator new; Using standardnewdeleteforms::operator delete; Static void* operator new (std::size_t size, std::ostream& logstream) throw (std::bad_alloc); static void operator Delete (void* pmemory, std::ostream& logstream) throw (); };
Please remember:
(1) When you write a placement operator new, make sure that the corresponding placement operator delete is also written. If this is not done. Your program may have a memory leak that is hidden and is intermittent.
(2) When you declare placement new and placement Delete. Be sure not to inadvertently obscure their normal version.
Effective C + + clause 52: wrote placement new also to write placement delete