Clause 8: When writing operator new and operator Delete, it must follow the general rules.

Source: Internet
Author: User
Tags class operator
Clause 8: When writing operator new and operator Delete, it must follow the general rules.

 

When you rewrite operator new by yourself (cla10 explains why you sometimes rewrite it), it is important that the behavior provided by the function should be consistent with the default operator new of the system. In practice, the correct return value must be obtained. When the available memory is insufficient, the error processing function must be called (see Article 7). The 0-byte memory request must be processed. In addition, we should avoid hiding the standard form of new accidentally, but this is the topic of Clause 9.

The return value is simple. If the memory allocation request is successful, a pointer to the memory is returned. If the request fails, a STD: bad_alloc type exception is thrown in accordance with Clause 7.

But it is not that simple. Because operator new actually tries to allocate memory more than once, it needs to call the error processing function after each failure, and expects the error processing function to find a way to release memory elsewhere. Operator new throws an exception only when the pointer to the error processing function is null.

In addition, the C ++ standard requires that operator new returns a valid pointer even when the request is allocated 0 bytes of memory. (In fact, this strange requirement does make C ++ easy to use elsewhere)

In this way, the pseudo code of operator new in the form of non-class members looks like the following:
Void * operator new (size_t size) // operator new may have other parameters
{

If (size = 0) {// when processing 0-byte requests,
Size = 1; // it is processed as a 1-byte request.
}
While (1 ){
Allocate size bytes of memory;

If (allocated successfully)
Return (pointer to memory );

// The allocation fails. Find the processing function for the current error.
New_handler globalhandler = set_new_handler (0 );
Set_new_handler (globalhandler );

If (globalhandler) (* globalhandler )();
Else throw STD: bad_alloc ();
}
}

The trick to process a zero-byte request is to process the request as one byte. This seems strange, but simple, legal, and effective. How often will you encounter a zero-byte request?

You may wonder why the error handler is set to 0 in the above pseudo code and then immediately restored. This is because there is no way to directly obtain the pointer to the error handler function, so it must be found by calling set_new_handler. The solution is stupid but effective.

Article 7 mentions that operator new contains an infinite loop, and the code above clearly illustrates this -- while (1) will lead to an infinite loop. The only way out of the loop is to successfully allocate the memory or handle errors. The Handler completes one of the events described in clause 7: get more available memory; A New-handler (error handler) is installed; New-handler is removed; an STD: bad_alloc or its derived type exception is thrown; or an error is returned. Now I understand why New-handler must do one of these jobs. If you do not do this, the loop in operator new will not end.

Many people do not realize that operator new often inherits quilt classes. This may cause some complexity. In the above pseudo code, the function will allocate size bytes of memory (unless the size is 0 ). Size is very important because it is a parameter passed to the function. But most operator new (including the one in Clause 10) written for classes are designed only for specific classes, not for all classes, nor for all their subclasses. This means that for operator new of Class X, the internal behavior of the function is precise sizeof (x) when it comes to objects: Not big or small. However, due to inheritance, operator new in the base class may be called to allocate memory for a subclass object:
Class base {
Public:
Static void * operator new (size_t size );
...
};

Class derived: public base // The derived class does not declare operator new
{...};//

Derived * P = new derived; // call base: Operator new

If the base-class operator new doesn't need to work hard to handle this situation-this is unlikely to happen-the simplest way is to transfer the memory allocation requests with this "error" quantity to the standard operator new to handle, as follows:
Void * base: Operator new (size_t size)
{
If (size! = Sizeof (base) // if the number is "Incorrect", make the standard operator new
Return: Operator new (size); // process the request
//

... // Otherwise process this request
}

"Stop !" I heard you say, "You forgot to check a situation that is unreasonable but may happen-the size may be zero !" Yes, I didn't check it, but please don't say that again next time. :) But the check is actually done, but it is integrated into size! = Sizeof (base) statement. The C ++ standard is weird. One of them is that the size of the independent (freestanding) class is non-zero. Therefore, sizeof (base) is never zero (even if the base class has no members). If the size is zero, the request will go to: Operator new, it is used to process requests in a reasonable way. (Interestingly, if the base is not an independent class, sizeof (base) may be zero. For details, see "my article on counting objects ").

To control the memory allocation of class-based arrays, you must implement the array form of operator New-operator new [] (this function is often called "array new ", because I cannot figure out how to pronounce "operator new ). When writing operator new [], remember that you are facing the "original" memory and cannot perform any operation on objects that do not exist in the group. In fact, you do not even know the number of objects in the array, because you do not know the size of each object. Operator new [] of the base class is used to allocate memory to the array of the subclass object through inheritance, and the subclass object is usually larger than the base class. Therefore, it cannot be assumed that the size of each object in base: Operator new [] is sizeof (base). That is to say, the number of objects in the array is not necessarily (number of request bytes) /sizeof (base ). For a detailed introduction to operator new [], refer to the Terms and Conditions M8.

All the conventions to be followed when operator new (and operator new []) is rewritten. Operator Delete (and its partner operator Delete []) is simpler. All you need to remember is that C ++ ensures that deleting null pointers is always safe, so you must fully apply this guarantee. The following is a pseudo code of operator Delete, which is a non-class member:
Void operator Delete (void * rawmemory)
{
If (rawmemory = 0) return; file: // If/if the pointer is null, return
//

Releases the memory pointed to by rawmemory;

Return;
}

The class member version of this function is also simple, but the size of the deleted object must be checked. Assume that operator new of the class transfers the allocation request of "error" size to: Operator new, the deletion request of "error" size must also be transferred to: Operator delete:

Class base {// same as the previous one, but it is declared here
Public: // operator Delete
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 the NULL pointer

If (size! = Sizeof (base) {// If the size is "Incorrect ",
: Operator Delete (rawmemory); // Let the standard operator handle the request
Return;
}

Releases the memory directed to rawmemory;

Return;
}

It can be seen that the operator new and operator Delete (and their array form) Regulations are not so troublesome, and it is important to comply with them. As long as the memory allocation program supports the new-handler function and correctly processes the zero-memory request, it is almost the same. If the memory release program processes a null pointer, there is nothing else to do. Adding inheritance support to functions of the class member version will soon be completed.


Clause 9: Avoid hiding the standard form of new

 

Because the name of the internal range declaration hides the Same Name of the external range

For function f with the same name as the global declaration, the member function of the class hides the global function:

Void F (); // global function

Class X {
Public:
Void F (); // member function
};

X;

F (); // call f

X. F (); // call X: F

This will not be surprising or cause confusion, because global functions and member functions are always called differently.

Syntax format. However, if you add an operator new function with multiple parameters in the class, the result will be:

It may be surprising.

Class X {
Public:
Void F ();

// Specify a parameter for operator new
// New-hander (New error handling) Function
Static void * operator new (size_t size, new_handler P );
};

Void specialerrorhandler (); // defined elsewhere

X * px1 =
New (specialerrorhandler) x; // call X: Operator new

X * px2 = new x; // error!

After defining a function called "operator new" in the class, it will inadvertently block access to the standard new

Q. Clause 50 explains why this is the case. Here we are more concerned about how to find a way to avoid this problem.

One way is to write an operator new in the class that supports the standard new call method, which is the same as the standard new method.

. This can be achieved through an efficient inline function.

Class X {
Public:
Void F ();

Static void * operator new (size_t size, new_handler P );

Static void * operator new (size_t size)
{Return: Operator new (size );}
};

X * px1 =
New (specialerrorhandler) x; // call X: Operator
// New (size_t, new_handler)

X * px2 = new x; // call X: Operator
// New (size_t)

Another way is to provide the default value for each parameter added to operator new (see article 24 ):

Class X {
Public:
Void F ();

Static
Void * operator new (size_t size, // P default value: 0
New_handler p = 0 );//
};

X * px1 = new (specialerrorhandler) x; // correct

X * px2 = new x; // correct

Either way, if you want to customize new functions in the "standard" form in the future, you only need to rewrite this function.

The caller can use the new function after re-compiling the link.

Clause 10: Write operator delete at the same time if operator new is written.

 

Let's look back at the basic question: why is it necessary to write your own operator new and operator delete?

The answer is usually: for efficiency. The default operator new and operator Delete have a very good versatility. This flexibility also makes it possible to further improve its performance in certain situations. This is especially true for applications that require Dynamic Allocation of large but small objects.

For example, there is a class indicating an airplane: the class airplane contains only one pointer, which points to the actual description of the aircraft object (this technology is described in Clause 34 ):

Class airplanerep {...}; // represents an airplane object.
//
Class airplane {
Public:
...
PRIVATE:
Airplanerep * rep; // indicates the actual description.
};

An airplane object is not large. It contains only one pointer (as described in articles 14 and M24, if the airplane class declares a virtual function, it implicitly contains the second pointer ). However, when operator new is called to allocate an airplane object, it may require more memory than storing this pointer (or a pair of pointers. This strange behavior occurs because information must be transmitted between operator new and operator Delete.

Because the default version of operator new is a general-purpose memory distributor, it must be able to allocate any size of memory blocks. Similarly, operator Delete can release memory blocks of any size. Operator Delete needs to know the memory size allocated by operator new to determine the memory size to be released. There is a common method for operator new to tell operator Delete the size of the originally allocated memory, that is, some additional information is included in the memory it returns, used to specify the size of the allocated memory block. That is to say, when you write the following statement,

Airplane * pA = new airplane;

You won't get a memory block that looks like this:

Pa --> airplane Object Memory

Instead, we get a memory block like this:

Pa --> memory block size data + airplane Object Memory

For very small objects such as airplane, these extra data information doubles the memory size required for dynamic object allocation (especially when no virtual function is available in the class ).

If the software runs in a very valuable memory environment, it will not be able to afford this luxury Memory Allocation Solution. Write an operator new for the airplane class. You can use the features of equal size of each airplane without adding additional information to each allocated memory block.

Specifically, there is a way to implement your custom operator New: First let the default operator new allocate some large blocks of original memory, each of which is sufficient to accommodate many airplane objects. The memory blocks of the airplane object are taken from these large memory blocks. Currently unused memory blocks are organized into linked lists-called free linked lists-for future airplane use. It sounds like every object bears the overhead of a next field (used to support linked lists), but it does not: the rep domain space is also used to store the next pointer (because only the memory block used as the airplane object requires the rep pointer; similarly, the next pointer is required only for memory blocks that are not used as airplane objects. This can be implemented using union.

You must modify the airplane definition to support custom memory management. You can do this:

Class airplane {// modified class-supports custom Memory Management
Public ://

Static void * operator new (size_t size );

...

PRIVATE:
Union {
Airplanerep * rep; // the object to be used.
Airplane * Next; // used for unused (in a free linked list) Objects
};

// A constant of the class, specifying how many memory blocks are put
// Airplane object, which is initialized later
Static const int block_size;

Static airplane * headoffreelist;

};

The code above adds several declarations: An operator new function, a Union (so that the rep and next fields occupy the same space), a constant (specify the size of a large memory block ), A static pointer (tracking the header of the Free linked list ). It is important to declare the header pointer as a static member, because the entire class has only one free linked list, instead of every airplane object.

The operator new function should be written below:

Void * airplane: Operator new (size_t size)
{
// Transfer the "error" request to: Operator new () for processing;
// For details, see clause 8.
If (size! = Sizeof (airplane ))
Return: Operator new (size );

Airplane * P = // P points to the header of the Free linked list
Headoffreelist ;//

// If P is valid, the header is moved to its next element.
//
If (P)
Headoffreelist = p-> next;

Else {
// If the free linked list is empty, a large memory block is allocated,
// Supports block_size airplane objects
Airplane * newblock =
Static_cast <airplane *> (: Operator new (block_size *
Sizeof (airplane )));

// Link each small memory block to form a new free linked list
// Skip 0th elements because it will be returned to the caller of operator new
//
For (INT I = 1; I <block_size-1; ++ I)
Newblock [I]. Next = & newblock [I + 1];

// End the linked list with a null pointer
Newblock [block_size-1]. Next = 0;

// P is set as the table header, and headoffreelist points
// Keep the memory block behind it
P = newblock;
Headoffreelist = & newblock [1];
}

Return P;
}

If you read clause 8, you will know that when operator new cannot meet the memory allocation request, it will execute a series of routine actions related to the new-handler function and exceptions. The above Code does not have these steps, because the memory managed by operator new is allocated from: Operator new. This means that operator new will fail only when: Operator new fails. If: Operator new fails, it will execute the New-handler action (may end with an exception thrown at last), so it does not need to be processed by airplane operator new. In other words, the new-handler action is still there. You just don't see it. It is hidden in: Operator new.

With operator new, the following is how to define static data members of airplane:

Airplane * airplane: headoffreelist;

Const int airplane: block_size = 512;

You do not need to explicitly set headoffreelist to a null pointer because the initial values of static members are set to 0 by default. Block_size determines the size of the memory block to be obtained from: Operator new.

Operator new of this version will work very well. It allocates less memory to the airplane object than the default operator new, and runs faster, which may be a level 2 to the power. It's no surprise that the general-purpose default operator new must handle memory requests of various sizes and also process internal and external fragments. Your operator new only uses a pair of pointers in the linked list. Flexibility can be easily converted to speed.

Next we will discuss operator Delete. Do you still remember operator delete? These terms are about operator Delete. However, until now, the airplane class only declares operator new and has not declared operator Delete. Think about what will happen if you write the following code:

Airplane * pA = new airplane; // call
// Airplane: Operator new
...

Delete Pa; // call: Operator Delete

When you read this code, if you raise your ears, you will hear the sound of a plane crashing and burning, and the cries of programmers. The problem is that operator new (the one defined in airplane) returns a memory pointer with no leading information, while operator Delete (the default one) it is assumed that the memory that is passed to it contains header information. This is the cause of the tragedy.

This example illustrates the general principle that operator new and operator delete must be written at the same time so that different assumptions do not exist. If you write your own memory allocation program, you need to write a release program at the same time. (For another reason for following this rule, see the sidebar on Placement Section in article on counting objects)

Further design of the airplane class is as follows:

Class airplane {// same as the previous one, but adds
Public: // operator Delete statement
...

Static void operator Delete (void * deadobject,
Size_t size );

};

// It is a memory block that is passed to operator Delete. If
// If its size is correct, add it to the front of the free memory block linked list
//
Void airplane: Operator Delete (void * deadobject,
Size_t size)
{
If (deadobject = 0) return; // See Clause 8.

If (size! = Sizeof (airplane) {// See Clause 8.
: Operator Delete (deadobject );
Return;
}

Airplane * carcass =
Static_cast <airplane *> (deadobject );

Carcass-> next = headoffreelist;
Headoffreelist = carcass;
}

Because the request with the "error" size was previously transferred to the global operator new in operator new (see Article 8 ), here, we also need to hand over the "error" object to the global operator delete for processing. If this is not the case, it will repeat the kind of problem you have carefully avoided-the syntactic mismatch between new and delete.

Interestingly, if the object to be deleted is inherited from a class without virtual destructor, The size_t value passed to operator Delete may be incorrect. This is the reason that the base class must have a virtual destructor. In addition, Clause 14 also lists the second reason with more adequate reasons. Here, you just need to remember that operator Delete may not work properly if the base class misses a virtual constructor.

Everything works fine, but from the frown you frown, I can know that you are definitely worried about memory leakage. With a lot of development experience, you won't notice that airplane's operator new call: Operator new gets a large amount of memory, but airplane's operator Delete does not release them. Memory leakage! Memory leakage! I have clearly heard the alarm bells echo in your mind.

But please listen to my answer carefully. There is no memory leakage here!

Memory leakage occurs because the pointer to the memory is lost after the memory is allocated. If there is no mechanism for garbage processing or other languages, the memory will not be reclaimed. However, the above design does not expose memory because it will never cause memory pointer loss. Each large memory block is first divided into small pieces of airplane size and then placed on a free linked list. When the customer calls airplane: Operator new, the small part is removed from the free linked list, and the customer gets a pointer to the small part. When the customer calls operator Delete, the small pieces are put back on the free linked list. With this design, all memory blocks are not used by airplane objects (in this case, the customer is responsible for avoiding Memory leakage ), or on the free linked list (in this case, the memory block has a pointer ). So there is no memory leakage.

However, the memory block returned by: Operator new is never released by airplane: Operator Delete. This memory block has a name called memory pool. However, there is an important difference between memory leakage and memory pool. Memory leakage will increase infinitely, even if the customer follows the rules, and the size of the memory pool will never exceed the maximum memory requested by the customer.

Modifying the airplane memory management program makes it not difficult to automatically release the memory block returned by: Operator new when it is not used, but it will not be done here, for two reasons:

The first reason is the original intention of custom memory management. You have many reasons to customize memory management. The most basic one is that you confirm that the default operator new and operator Delete use too much memory or (and) run slowly. Compared with the memory pool policy, tracking and releasing each additional byte and each additional statement written by those large memory blocks will lead to slower software running and more memory used. When designing a library or program with high performance requirements, if you expect the memory pool size to be within a reasonable range, it would be better to use the memory pool method.

The second reason is related to the handling of some unreasonable procedural behaviors. Assuming that the airplane memory management program has been modified, airplane's operator Delete can release large blocks of memory without objects. Let's look at the following program:

Int main ()
{
Airplane * pA = new airplane; // the first allocation: get large memory,
// Generate a free linked list.

Delete Pa; // The memory block is empty;
// Release it

Pa = new airplane; // obtain large memory again,
// Generate a free linked list.

Delete Pa; // The memory block is empty again,
// Release

... // You have an idea...

Return 0;
}

This bad small program will run slowly and occupy more memory than the program written with the default operator new and operator Delete, let alone the program written with the memory pool.

Of course, there is a way to deal with this unreasonable situation, but the more special circumstances you consider, the more likely you will need to re-implement the memory management function. What will you get in the end? The memory pool cannot solve all memory management problems and is suitable in many cases.

In actual development, you will often implement the memory pool-based function for many different classes. You will think, "Is there any way to encapsulate this fixed-size memory distributor for easy use ". Yes, there is a way. Although I have been nagging about this term for so long, I 'd like to briefly introduce it and leave it to readers for exercises.

The following provides a minimum interface for the pool class (see clause 18). Each object in the pool class is a memory distributor of a certain type of object (its size is specified in the pool constructor.

Class pool {
Public:
Pool (size_t N); // creates an object whose size is N.
// A distributor

Void * alloc (size_t N); // allocate enough memory for an object
// Operator New Convention following clause 8

Void free (void * P, size_t N); // return the memory referred to by P to the memory pool;
// Follow operator Delete conventions in Clause 8

~ Pool (); // release all memory in the memory pool

};

This class supports the creation of pool objects, the allocation and release operations, and the destruction. When the pool object is destroyed, all memory allocated by it is released. That is to say, there is a way to avoid the memory leakage in the airplane function. However, this also means that if the pool destructor call is too fast (the objects using the memory pool are not all destroyed), some objects will find that the memory they are using is suddenly gone. The result is usually unpredictable.

With this pool class, even Java programmers can easily add their own memory management functions in the airplane class:

Class airplane {
Public:

... // General airplane Function

Static void * operator new (size_t size );
Static void operator Delete (void * P, size_t size );

PRIVATE:
Airplanerep * rep; // pointer to the actual description
Static pool mempool; // airplanes Memory Pool

};

Inline void * airplane: Operator new (size_t size)
{Return mempool. alloc (size );}

Inline void airplane: Operator Delete (void * P,
Size_t size)
{Mempool. Free (p, size );}

// Create a memory pool for the airplane object,
// Implemented in the class implementation file
Pool airplane: mempool (sizeof (airplane ));

This design is much clearer and cleaner than the previous one, because the airplane class is no longer mixed with non-airplane code. Union, the head pointer of the Free linked list, the constants defining the size of the original memory block are gone, and they are all hidden in the pool class where they should stay. Let write pool programmers worry about the details of memory management. Your job is to make the airplane class work normally.

Now we should understand that custom memory management programs can improve program performance and they can be encapsulated in classes like pool. But do not forget the main point. Operator new and operator delete must work at the same time. If you write operator new, you must also write operator Delete.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.