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.
From valid tive C ++