Effective C ++ 2e Item 10

zhaozj2021-02-11  199

Terms 10. If you write Operator New, you should write Operator Delete at the same time.

Let us go back to see such a 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 very good versatility, which also makes it possible to further improve its performance in some specific occasions. This is especially true in applications that need to dynamically allocate a large amount of but small objects.

For example, there is such a class that represents aircraft: class AirPlane only includes a pointer, which pointing is the actual description of the aircraft object (this technology is described in terms 34):

Class AirPlanerep {...}; // Represents a plane object // class airplane {public: ... private: airplanerep * rep; // points to the actual description};

An AirPlane object is not large, it only contains a pointer (as explained in terms 14 and m24, if the AirPlane class declares the virtual function, implicitly containing the second pointer). However, when the Operator New is called to assign an AirPlane object, the obtained memory may be more than the need to store this pointer (or a pair of pointers). The reason why this seems strange behavior is that there is a need to pass information between Operator New and Operator delete.

Because the default version of Operator New is a universal memory distributor, it must be assigned a bundle memory block. Similarly, Operator delete also can release any of the small memory blocks. Operator delete wants to find out how much it is to be released, you must know how much the memory allocated by Operator NEW is. There is a common way to let Operator New told Operator DELETE how much the memory size is, which is included in the memory it returns, which is used to indicate 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 block that looks like this:

PA -> Aircplace object's memory

But get memory blocks like this:

PA -> Memory block size data airplane object memory

For objects like AirPlane, these additional data information will double the memory required by the dynamically assigned object (especially when there is no virtual function in the class).

If the software runs in a very valuable environment, you can't afford this extravagant memory allocation scheme. To write an Operator New for the AirPlane class, you can use each AirPlane's size, and you don't have to add an inclusive information on each assigned memory block.

Specifically, there is such a method to implement your custom Operator New: Let the default Operator New allocate some large block of original memory, each of which is enough to accommodate a lot of AirPlane objects. The memory block of the AirPlane object is taken from these large memory blocks. The memory block that is currently not used is organized into a chain table - called a free linked list - in use for future airplane. It sounds like each object to assume a NEXT domain overhead (for supporting the list), but it will not: the space of the REP domain is also used to store the NEXT pointer (because only the memory blocks used as the AirPlane object. REP pointer; Similarly, only NEXT pointers are needed for memory blocks used as AirPlane objects), which can be implemented with Union. When implementing, you must modify the definition of AirPlane to support custom memory management. Can do this:

Class Airplane {// Modified class - Support Custom Memory Management Public: //

Static void * Operator new (SIZE_T SIZE);

...

Private: UNION {AirPlanerep * rep; // The object airplane * next; // is used (in the free linked list) object};

// Class constant, specify how many // airplane objects in a large memory block, initialize Static Const Int Block_size later;

Static airplane * headoffreeelist;

}

A few declarations added above: an Operator new function, a combination (make the same space occupying the REP and NEXT domain), a constant (specify the size of the large memory block), a static pointer (tracking the watch of the free linked list) ). The head pointer declares that the static member is important because the entire class has only one free linked list, not every AirPlane object.

Let's write the Operator New function:

Void * Airplane :: Operator new (size_t size) {// Put the "error" request to :: operator new () processing; // See Terms 8 IF (size! = sizeof (airplane)) Return :: Operator new (size);

AirPlane * P = //p Points to the free linked table headoffreeList; //

// p If it is legal, move the head to its next element // if (p) headoffreelist = p-> next;

ELSE {// Free linked list is empty, assign a large memory block, / / ​​can accommodate block_size an airplane object AirPlane * newblock = static_cast (: Operator new (block_size * sizeof (airplane)))));

// Link each small memory block to form a new free linked list // skip the 0th element because it is to be returned to the caller // for (int i = 1; i

// P is set to the head of the table, and the // memory block pointing to Headoffreelist is followed by it later P = newblock; Headoffreelist = & newblock [1];

Return P;

If you read the terms 8, you will know that a series of routine action related to the New-Handler function and exception is performed when Operator New cannot meet the memory allocation request. The above code does not have these steps because the memory of Operator New is assigned from :: Operator New. This means that only :: Operator New fails, the Operator New will fail. And if :: operator new failed, it will perform the action of New-handler (may end up with an exception end), so it does not require AirPlane's Operator New also deal with it. In other words, in fact, New-handler's movements are still there, you just didn't see it, it hides :: Operator New.

With Operator New, what is going on below is to give the definition of a static data member of AirPlane:

AirPlane * AirPlane :: Headoffreelist; const Int airplane :: block_size = 512; there is no need to explicitly set the headoffreelist to an empty pointer because the initial value of static members is set to zero. Block_size determines how much memory blocks you want to get from :: Operator NEW.

This version of Operator New will work very well. It assigns a memory that is allocated by the AirPlane object to less than the available Operator New, and it is run faster and may be fast 2 times. This is not strange, universal default Operator New must deal with a variety of memory requests, and handle internal exterior debris; and your Operator New uses a pair of pointers in the operation list. Abandoning flexibility can often be easily exchanged.

Below we will discuss Operator Delete. Remember the operator delete? This Terms are discussions about Operator Delete. But until now, the AirPlane class only declares how Operator New has not yet stated that Operator delete. Think about what happens if the following code is written:

Airplane * PA = new airplane; // call // AirPlane :: Operator new ...

DELETE PA; // Call :: Operator Delete

When you read this code, if you erect your ears, you will hear the sound of the aircraft crashed, and there are programmers cry. The problem has returned a pointer to the memory that does not take the header information in Operator New (the one defined in airplane), and the Operator Delete is assumed to pass the memory containing the header information. This is the cause of the tragedy. This example illustrates a universal principle: Operator New and Operator Delete must be written at the same time, so there will be no different assumptions. If you write your own memory allocation, you should write a release program at the same time. (For another reason why you want to follow this provision, see the Sidebar on Placement chapter in Article on Counting Objects)

Therefore, the Airplane class is continued as follows:

Class Airplane {//, like front, but add a public: // Operator Delete's declaration ...

Static void Operator Delete (void * deadObject, size_t size);

}

// Passing to Operator Delete is a memory block, if // is correct, add to the front // 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 (deadObject);

Carcass-> next = headoffreeelist; headoffreeelist = carcass;

Since the "Error" request to the global Operator New in the Operator New, the "Error" size is handed over to the global Operator Delete to handle it. If so, you will reproduce the problem that your foresight is always thought - the mismatch onnew and delete syntax.

Interestingly, if the object to be deleted is inherited from a class without a false argument function, it may be incorrect. This is to ensure that the base class must have a deficit function, and the provisions 14 also list the second, reasonable reason. As long as you simply remember, if you miss the virtual structure function, Operator delete may be incorrect.

Everything is very good, but I can know that you must be worried about memory leaks from your frown. You won't notice a lot of development experience, AirPlane's Operator New Call :: Operator New Get big memory, but AirPlane's Operator delete did not release them. Memory leak! Memory leak! I have fallen out that the alarm is echo in your mind.

But please listen carefully, answer, there is no memory and disclosure here!

The reason for causing memory leaks is lost by memory allocation. These memory will not be recovered if there is no mechanism other than garbage disposal or other languages. But the above design does not have memory leaks because it will never be lost in memory pointers. Each large memory block is first divided into small pieces of the AirPlane size, then these small pieces are placed on the free linked list. When the customer calls AirPlane :: Operator New, the small block is removed from the free linked list, and the customer gets a pointer to the small block. When the customer calls Operator Delete, the small block is placed back to the free linked list. With this design, all memory blocks are not used by the AirPlane object (in this case, it is responsible for avoiding memory leaks), or not on the free linked list (in this case, the memory block has a pointer). So there is no memory leak here. However, ::: Operator New Returned Memory blocks have never been released by AirPlane :: Operator Delete, this memory block has a name called memory pool. But memory leaks and memory pools have an important difference. Memory leaks will increase infinitely, even if the customer follows the rules; the memory pool is not exceeded to the maximum of the customer request memory.

Modify the AirPlane's memory management program makes :: Operator New's memory block is automatically released when it is not used, but it will not do this here, this has two reasons:

The first reason is related to the original intent of your 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) very slow. Compared with the memory pool policy, track and release each additional byte written in large memory blocks and each additional statement will cause the software to run slower, more memory. When designing performance is required to have a high library or program, if you expect the size of the memory pool to be within a reasonable range, the method of using the memory pool is better.

The second cause is related to some unreasonable program behavior. Assuming that AirPlane's memory management program is modified, AirPlane's Operator Delete can release any large blocks that have no objects. That look at the procedure below:

INT main () {airplane * pa = new airplane; // First assignment: get big block memory, // Generate free linked list, etc.

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

PA = new airplane; // get a large block memory again, // Generate a free linked list, etc.

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

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

Return 0;}

This bad maker will run slower than the programs written by the default Operator New and Operator Delete, which takes up more memory, not to write with the memory pool.

Of course, there is a way to deal with this unreasonable situation, the more special circumstances, the more likely to re-implement memory management functions, and what will you get? Memory pools cannot solve all memory management issues, which is very suitable in many cases.

In actual development, you will often give many different classes to the memory pool. You will think, "Do you have any way to encapsulate this fixed-size memory distributor, which can be easily used." Yes, there is a way. Although I have been in this article, I still have to introduce it to the reader to practice. The minimum interface for a Pool class is given below (see Terms 18), and each object of the Pool class is a memory distributor of a certain type of object (specified in the constructor of the pool).

Class pool {public: pool (size_t n); // Create // an allocator for an object of size N

Void * alloc (size_t n); // Assign a sufficient memory / / Operator New regular in the article 8

Void free (void * p, size_t n); // returns the memory fingered by P to the memory pool; // Follow the Operator Delete regular regularity of the clause 8

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

}

This class supports the creation of the pool object, performs allocation and release operations, and destroyed. When the POOL object is destroyed, all memory it assigns is released. That is to say, there is now a way to avoid the missed behavior of the memory leaks in the AirPlane function. However, this also means that if the Pool's destructive function call is too fast (there is not all destroyed objects using the memory pool), some objects will find that the memory it is in use, there is no time. This result is usually unpredictable.

With this pool class, even if the Java programmer can add your own memory management function in the AirPlane class without blowing ash:

Class AirPlane {public:

... // Ordinary 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 memory; // airplanes memory pool

}

Inline void * airplane :: operator new (size_t size) {returnempool.alloc (size);

Inline void airplane :: Operator delete (void * p, size_t size) {mempool.free (p, size);}

/ / Create a memory pool for the AirPlane object, // Implement pool airplane :: Mempool (Sizeof (airplane) in the implementation file of the class;

This design is much clear than the front, because the AirPlane class is no longer mixed with the non-AirPlane code. Union, free-linked head pointers, define constants of the original memory block size, and they are hidden in the POOL class that they should stay. Let the Pool's programmer to worry about the details of memory management, your job is just working properly.

转载请注明原文地址:https://www.9cbs.com/read-4467.html

New Post(0)