10.1 clause 49: understand New-handler behavior (understand the behavior of the New-handler)
Before operator new throws an exception to reflect an unfulfilled memory requirement, it calls an error handler specified by the customer, a so-called new-handler. Set_new_handler interface of the standard library function <New>:
Namespace STD {
Typedef void (* new_handler )();
New_handler set_new_handler (new_handler p) Throw ();
}
New_handler is a typedef and defines a pointer pointing to a function. This function has no parameters and does not return anything. Set_new_handler is the function of "getting a new_handler and returning a new_handler. The set_new_handler declarative tail "Throw ()" is an exception details, indicating that the function does not throw any exceptions.
The set_new_handler parameter is a pointer pointing to the function called when operator new cannot allocate enough memory. The returned value is also a pointer to the new_handler function that is being pointed to before set_new_handler is called.
To design a good new_handler function, you must do the following:
1) make more memory available. For example, the program allocates a large block of memory at the beginning of execution, and then releases new_handler to the program when it is called for the first time.
2) install another new_handler. Find a new_handler that can get more memory.
3) Remove new_handler, that is, pass the NULL pointer to set_new_handler. Once no new_handler is installed, operator new throws an exception when the memory allocation fails.
4) throw an exception of bad_alloc (or derived from bad_alloc.
5) If no result is returned, abort or exit is usually called.
C ++ does not support new_handlers exclusive to the class. You need to design a widget class to deal with this requirement.
Class widget {
Public:
Static STD: new_handler set_new_handler (STD: new_handler p) Throw ();
Static void * operator new (STD: size_t size) Throw (STD: bad_alloc );
PRIVATE:
Static STD: new_handler currenthandler;
};
// The static member must be defined outside the class definition and initialized to null in the class implementation file.
STD: new_handler Widget: currenthandler = 0;
Static STD: new_handler Widget: set_new_handler (STD: new_handler p) Throw ()
{
STD: new_handler oldhandler = currenthandler;
Currenthandler = P;
Return oldhandler;
}
Void * Widget: Operator new (STD: size_t size) Throw (STD: bad_alloc)
{// Install new_hander of the widget
Newhandlerholder H (STD: new_handler (currenthandler ));
Return: Operator new (size); // allocate memory or throw an exception to restore global new_handler
}
Example 10-1-1 class exclusive new_handler
The operator new of the widget does the following:
1) Call the standard set_new_handler to notify the error handling function of the widget.
2) Call global operator new to execute the actual memory allocation. If the allocation fails, global operator new calls the new_handler of the widget. If the global operator new cannot allocate enough memory, a bad_alloc exception will be thrown.
3) if global operator new can allocate enough memory for a widget object, the operator new of the widget will return a pointer to execute the allocation.
The following is a further explanation of the C ++ code:
Class newhandlerholder {
Public:
Explicit newhandlerholder (STD: new_handler NH): handler (NH) {}// get the current new_handler
~ Newhandlerholder ()
{STD: set_new_handler (handler);} // release it
Static STD: new_handler set_new_handler (STD: new_handler p) Throw ();
Static void * operator new (STD: size_t size) Throw (STD: bad_alloc );
PRIVATE:
STD: new_handler handler;
Newhandlerholder (const newhandlerholder &); // block coping, clause 4
Newhandlerholder & operator = (const newhandlerholder &);
};
// The static member must be defined outside the class definition and initialized to null in the class implementation file.
STD: new_handler Widget: currenthandler = 0;
Static STD: new_handler Widget: set_new_handler (STD: new_handler p) Throw ()
{
STD: new_handler oldhandler = currenthandler;
Currenthandler = P;
Return oldhandler;
}
Void outofmem ();
Widget: set_new_handler (outofmem );
Widget * pw1 = new widget; // if the allocation fails, call outofmem
STD: string * PS = new STD: string; // if the allocation fails, call the global new_handler function.
Widget: set_new_handler (0 );
Widget * pw2 = new widget; // if the allocation fails, an exception is thrown immediately.
Example 10-1-2 the next chapter of the class exclusive new_handler
Assume that the above scheme is changed to base class to inherit the derived class, and then convert the base class into a template, so that each derived class will obtain a class data replica with different entities. The base class section of this design allows Derived classes to inherit the set_new_handler and operator new required by them, while the template section reports that each derived class obtains an object with different currenthandler member variables. As follows:
Template <typename T>
Class newhandlersupport {// set_new_handler exclusive to the class
Public:
Static STD: new_handler set_new_handler (STD: new_handler p) Throw ();
Static void * operator new (STD: size_t size) Throw (STD: bad_alloc );
...
PRIVATE:
STD: new_handler handler;
};
Template <typename T>
STD: new_handler newhandlersupport <t>: set_new_handler (STD: new_handler p) Throw ();
{
STD: new_handler oldhandler = currenthandler;
Currenthandler = P;
Return oldhandler;
}
Template <typename T>
Void * newhandlersupport <t>: operatornew (STD: size_t size) Throw (STD: bad_alloc)
{// Install new_hander of the widget
Newhandlerholder H (STD: new_handler (currenthandler ));
Return: Operator new (size); // allocate memory or throw an exception to restore global new_handler
}
// Each of the following currenthandler initializes null
Template <typename T>
STD: new_handler newhandlersupport <t >:: currenthandler = 0;
Example 10-1-3 class exclusive new_handler Template
With this class template, it is much easier to add set_new_handler support for the widget. As long as the widget inherits from newhandlersupport <widget>, OK.
Class Widget: Public newhandlersupport <widget>
{// Same as before, but it does not need to declare set_new_handler or operator new
...
};
The template mechanism automatically generates a copy of currenthandler for each t.
The C ++ Standards Committee provides a form of operator new, which is responsible for supplying traditional "returns NULL if allocation fails" behavior. This form is called the "nothrow" form.
Widget * pw1 = new widget; // if the allocation fails, bad-alloc is thrown.
If (pw1 = NULL) // This test must fail
Widget * pw2 = new (STD: nothrow) widget; // if the allocation fails, 0 is returned.
If (pw2 = NULL) // this test may be successful
The expression "New (STD: nothrow) widget" has two things. First, operator new of nothrow is called. If the allocation fails, null is returned. Second, the widget constructor causes an exception. Therefore, there is no need to use nothrow new.
10.2 clause 50: Understand the reasonable replacement time for new and delete (understand when it makes sense to replace New and delete)
Common Reasons for replacing operator new or operator delete provided by the compiler:
1) used to detect application errors. If we create operator news on our own, we can overallocate the memory and place the specific byte pattern (signature, signature) with extra space ). Operator deletes can check whether the above signature is intact.
2) to improve efficiency. If you have a deep understanding of the dynamic memory allocation of the program, you can customize it by yourself.
3) To collect statistics.
4) to increase the distribution and return speed. The general-purpose splitter is often slower than the custom one.
5) in order to reduce the extra space overhead brought by the Memory Manager. Generic memory management usually uses more memory to differentiate the type.
6) suboptional alignment ).
7) in order to cluster related objects.
8) to obtain nontraditional behaviors. For example, allocate and return shared memory (share memory ). Use C ++ to encapsulate C APIs.
Article 51: Be aware of template metaprogramming)
Start with operator new. To implement consistent operator new, you must return the correct value. When the memory is insufficient, you must call the new handling function. You must be prepared to deal with the zero-memory requirement, and avoid overwriting the normal form of new.
The return value of operator new either returns a pointer to the memory or throws a bad_alloc exception. Operator new actually tries to allocate memory more than once and calls the new_handing function after each failure. When the pointer to the new_handing function is null, operator new throws an exception. Operator new contains an infinite loop. The only way to exit the loop is: memory allocation is successful, another new_handler is installed, new_handler is uninstalled, bad_alloc exception is thrown, or a direct return is returned when a failure is acknowledged. Clause 49.
When the customer requires 0 bytes, operator new also needs to return a valid pointer pointing to 1 byte space.
Assume that the operator new member function is inherited by Derived classes. For example
Class base {
Public:
Static void * operator new (STD: size_t size) Throw (STD: bad_alloc );
...
};
Class derived: public base {...}; // Derived does not declare operator new
Derived * Pd = new derived; // error. The base operator new is called here.
Example 10-3-1 operator new is inherited
The best practice for handling a wake-up error is to change the calling behavior of "memory application quantity error" to the standard operator new, as shown in the following code:
Void * base: Operator new (STD: size_t size) Throw (STD: bad_alloc)
{
If (size! = Sizeof (base ))
Return: Operator new (size );
...
}
If you decide to write an operator new [], the only thing you need to do is allocate a raw memory ).
C ++ ensures that "deleting null pointers is always safe", so the pseudo code of non-member operator delete:
Void operator Delete (void * pmem) Throw ()
{
If (pmem = 0) return;
// Return the memory now
...
}
The member version of this function is as follows:
Void operator base: delete (void * pmem, size_t size) Throw ()
{
If (pmem = 0) return;
If (size! = Sizeof (base )){
: Operator Delete (size );
Return;
}
// Return the memory now
...
}
10.4 clause 52: If you have entered placement new, you must also enter placement Delete (Be aware of template metaprogramming)
When you write a new expression like this:
Widget * PW = new widget;
There are two functions called: Operator New for separate memory and default constructor for widgets.
Assuming that the first function is successfully called, but the second function throws an exception, the responsibility for preventing Memory leakage falls into the C ++ runtime system. During runtime, the system calls the corresponding operator Delete version of operator new called in step 1, provided that it must know which operator Delete should be called.
Normal operator New:
Void * operator new (STD: size_t) Throw (STD: bad_alloc );
Corresponding to the normal operator delete:
Void * operator Delete (void * rawmem) Throw (STD: bad_alloc); // global scope
Void * operator new (void * rawmem, STD: size_t size) Throw (STD: bad_alloc); // class scope
If operator new accepts all the parameters except the size_t, this is the so-called placement new. Among the many placement new versions, it is particularly useful to "accept a pointer pointing to the object that is constructed", as shown below:
Void * operator new (STD: size_t, void * pmem) Throw ();
The new version is incorporated into the C ++ standard library. In general terms, "placement new" indicates a new with any additional parameter, because another term "placement Delete" is derived from it directly.
Suppose you have written a class-specific operator new, as shown below:
Class widget {
Public:
...
Static void * operator new (STD: size_t size, STD: ostream & logstream)
Throw (STD: bad_alloc); // an unusual new
Static void operator Delete (void * pmem, STD: size_t size );
Throw (); // normal class exclusive Delete
};
// Call operator new and pass cerr as its ostream real parameter,
// This action will leak the memory when the widget constructor throws an exception
Widget * PW = new (STD: cerr) widget;
Example 10-4-1 class exclusive operator new
During the runtime, the system finds an operator Delete with the same number and type as operator new. If it is found, it is called. In this example, operator new accepts an additional real parameter of the ostream type, so the corresponding operator Delete should be:
Void operator Delete (void *, STD: ostream &) Throw ();
Similar to the placement version of new, operator Delete is called Placement deletes if it accepts additional parameters.
Therefore, it is necessary for the class widget to declare a placement Delete, which corresponds to the placement new with the logging function.
After this change, the following situations occur:
Widget * PW = new (STD: cerr) widget;
Delete PW; // call the normal operator Delete
In the above cases, operator Delete is called in the normal form, rather than the placement version. Placement Delete is called only when an exception occurs in the "Constructor triggered with the call to placement new.
In addition, because the name of the member function masks the same name of its peripheral scope (clause 33 ), you must be careful not to make the class's exclusive news cover up other news (including normal versions) that the customer expects ).
Class base {
Public:
...
Static void * operator new (STD: size_t size, STD: ostream & logstream)
Throw (STD: bad_alloc); // This new will obscure the normal Global Form
...
};
Base * pb = new base; // error because normal operator new is masked
Base * pb = new (STD: cerr) base; // correct. The placement new of the base is called.
Class derived: public base {
Public:
...
Static void * operator new (STD: size_t size)
Throw (STD: bad_alloc); // re-declare the normal New
...
};
Derived * Pd = new (STD: clog) derived; // error because base placement new is masked
Derived * Pd = new derived; // correct. Call the base operator new
Example 10-4-2 is masked
Clause 33: C ++ provides operator new in the following form within the global scope by default for writing assignment functions:
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
Class standardbase {
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 * pmem) Throw ()
{Return: Operator Delete (pmem );}
// Placement new/delete
Static void * operator new (STD: size_t size, void * PTR) Throw ()
{Return: Operator new (size, PTR );}
Static void operator Delete (void * pmem, void * PTR) Throw ()
{Return: Operator Delete (pmem, 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 * pmem, const STD: nothrow_t & NT) Throw ()
{Return: Operator Delete (pmem );}
};
Class Widget: Public standardbase {
Public:
Using standardbase: Operator new;
Using standardbase: Operator Delete;
Static void * operator new (STD: size_t size, STD: ostream & logstream)
Throw (STD: bad_alloc); // an unusual new
Static void * operator Delete (void *, STD: ostream &) Throw ();
...
};
Widget * PW = new (STD: clog) widget; // OK
Widget * PW = new widget; // correct
Example 10-4-3 class exclusive new usage
This means that, to declare war on all memory leaks related to placement new, a normal operator delete must be provided at the same time (used to throw no exception during construction) and a placement version (used for throwing exceptions during construction ). The additional parameters of the latter must be the same as operator new.
11 other (Others) 11.1 c ++ keywords explicit
C ++ provides the keyword explicit to prevent implicit conversions that should not be allowed by conversion constructors. Constructors declared as explicit cannot be used in implicit conversions.
In C ++, the constructor of a parameter assumes two roles. 1 is a constructor 2 is a default and implicit type conversion operator.
So sometimes, when we write code like AAA = xxx, and exactly the xxx type is the parameter type of the Single-parameter constructor of AAA, the compiler will automatically call this constructor at this time, create an AAA object.