The management problem of memory involved in memory management C can be concluded to two aspects: it is correctly obtained and used effectively. A good programmer will understand why these two issues are listed in this order. Because the execution is fast, the program is small if it does not execute it as if you think, it is not used. "Correctly" means correctly calling memory allocation and release programs; "Effectively Using" is a memory allocation and release program that is written a particular version. Here, "correctly" seems to be more important. However, it is true that C has inherited a very serious headache from C, which is the hidden danger of memory. Virtual memory is a good invention, but virtual memory is limited, not everyone can grab it first. In C, as long as the memory allocated by Malloc is not returned with Free, memory leaks will be generated. In C , the name of the perpetrator is replaced with new and delete, but the situation is basically the same. Of course, because there is a session of the designer, the situation is slightly improved because the designer function provides a place where the object to be destroyed provides a convenient call. But this also brought more troubles because New and Delete are implicitly modulated constructor and destructive functions. And because the New and Delete operators can be defined within the class and the class, this has brought complexity and increasing an error. The following terms (also Terms M8) will tell you how to avoid the universal problems. Terms 5: What is wrong with the corresponding new and delete to use the same form? String * StringArray = new string [100]; ... delete stringArray; everything seems to be in order - a New corresponds to a delete, but hidden a lot of mistakes: the operation of the program will be unpredictable of. At least, 99 of the 100 String objects pointing to StringArray will not be properly destroyed because their destructor will never be called. Two things will occur when using New. First, the memory is assigned (via the Operator New function, see Terms 7-10 and Terms M8), then, one or more constructor is called for the assigned memory. When using Delete, there are two things happen: First, to call one or more destructor that will be released, then release memory (through the Operator Delete function, see Terms 8 and M8). There is an important issue for Delete: How many objects do you have to be deleted? The answer determines how many destructor will be called. This problem is simple to say: The pointer to be deleted is a single object, or an object array? This is only you tell Delete. If you don't use parentheses when you use Delete, Delete will think is a single object. Otherwise, it will think is an array: string * stringptr1 = new string; string * stringptr2 = new string [100]; .. delete stringptr1; // Delete an object delete [] StringPtr2; // Delete an array of objects If you add "[]" before StringPtr1 "[]"? The answer is: That will be unpredictable; if you didn't add "[]" before StringPtr2? The answer is: unpredictable. And for a fixed type like INT, the result is not predictable, even if such types have no destructive functions.
So, the rules that solve such problems are simple: if you call New [], use [] when calling Delete []. If you don't use [] when calling New, you don't use []. Keep in mind this rule when writing a class containing a pointer data member and providing multiple constructors. Because of this, you must use the same new form in the constructor of all initialization pointer members. Otherwise, what form of DELETE will be used in the destructor? Further explanation about this topic, see Terms 11. This rule is also important for people who like to use Typedef, because the programmers who write TypedEF must tell others, after using NEW to create a type of TypedEf defined type, what form of delete is used to delete. For example: typedef string addresslines [4]; // A person's address, a total of 4 lines, one String, one string because addressLines is an array, use new: string * pal = new addressline; // Note "New AddressLines" Return string * , And // "New String [4]" must be in the form of array in the form of array: Delete Pal; // Error! Delete [] PAL; // correctly to avoid confusion, it is best to prevent array types Typedefs. This is actually very easy, because the standard C library (see Terms 49) contains Stirng and Vector Templates, using them will reduce the demand for array to almost zero. For example, AddressLines can define a string (vector), ready to be a vector
So, when writing constructor, assigning operators, or other member functions, each pointer member of the class either points to a valid memory, or point to the empty, then you can use only simple Delete in your destructor. Drop them, don't worry, they are too many. Of course, don't use the use of this terms. For example, you certainly don't delete a pointer that is not initialized with New, and you will never delete a pointer to you when you use smart to delete it. In other words, unless the class member originally used New, it is not necessary to use Delete in the destructor. Speaking of intelligent pointers, here is introduced to avoid the method of deleting a pointer member, that is, replacing these members with intelligent pointer, such as Auto_PTR in the C standard library. Want to know how it works, look at the Terms M9 and M10. Terpsis 7: Pre-prepared memory is not enough, Operator New will throw an exception when the memory allocation request is not completed (former approach is usually returned 0, and some older compilers do this. You are willing to put you The compiler is set to do. About this topic I will postpone the discussion at the end of this Territor). Everyone knows that it is really a moral behavior that the abnormality that deals in memory is really a moral behavior, but it actually acts like a knife holder in the neck. So, sometimes you don't deserve it, maybe I have never deserved it. But your heart must still have a deep hidden sense of sin: What should I do if NEW really produces an abnormally? You will naturally think of a method of dealing with this situation, that is, go back to the previous old road and use pretreatment. For example, a common practice for c is to define a type-independent macro to allocate memory and check whether allocation is successful. For C , this macro looks like this: #define new (ptr, type) / try {(ptr) = new type;} / catch (std :: bad_alloc &) {assert (0);} ("slow What is it? "You will ask. BAD_ALLOC is how the Operator New does not meet the amount of exceptions thrown when the memory allocation request, STD is the name of the name space of Bad_alloc (see Terms 28)." Okay! " You will continue to ask, "What is the use of Assert?" If you look at the standard C header file
For example, want to create type T objects, there are usually three common grammatical forms, you must process the exceptions that may be generated for each form: New T (constructor arguments); new t [size]; Greatly simplified because someone also customizes (overloaded) Operator New, so the program will contain any syntax form using new NEW. So what should I do? If you want to use a very simple error handling method, you can do this: When the memory allocation request cannot meet, call your pre-specified error handling function. This method is based on a routine, that is, when the Operator New cannot meet the request, call the customer's specified error handler before thrown an exception - generally referred to as a new-handler function. (Operator New actually works to be complex, see Terms 8) Specify the set_new_handler function when the error handler is specified, which is roughly the following definition in the header file
In this case, the current new-handler can install another new-handler to replace it (by calling set_new_handler). The next time the Operator New is called New-handler, the most recently installed is used. (Another way to change this strategy is to let New-Handler change its own run behavior, then it will make different things when calling, it is to make New-Handler to modify the static status that affects its own behavior. Or global data.) · Remove New-Handler. That is, pass the empty pointer to set_new_handler. No new-handler is installed, the Operator New assigns memory that will throw a standard STD :: Bad_alloc type. • Throw std :: bad_alloc or other types of exceptions inherited from std :: bad_alloc. Such an exception will not be captured by the Operator New, so they will be sent to the place originally performed. (Throw other different types of exceptions will violate the Operator New anomalous specification. The default behavior in the specification is to call Abort, so when New-handler wants to throw an exception, be sure it is from std :: bad_alloc inherited from std :: bad_alloc Want to learn more about the exception regulations, see the Terms M14.) · Nothing returned. Typical practice is to call Abort or Exit. Abort / EXIT can be found in a standard C library (there are standard C libraries, see Terms 49). The above choice gives you great flexibility in the New-Handler function. What is the method taken when processing a memory allocation failure, depends on the object to be assigned, class: class x {public: static void outofmemory (); ...}; class y {public: static void outofmemory (); ... }; X * p1 = new x; // If the assignment is successful, call x :: outofMemory * p2 = new y; // If the assignment is unsuccessful, call Y :: OutofMemoryC does not support the NEW-Handler function specifically for classes, And it is not necessary. You can implement it yourself, just provide your own version of SET_NEW_HANDAL and OPERATOR New in each class. Class SET_NEW_HANDALER can specify new-handler for class (like standard set_new_handler specifying global new-handler). The Operator New guarantees the NEW-handler that replaces the global new-handler when assigning memory-type objects. Assume that the processing class X memory allocation failed. Because the Operator New is assigned memory failure to the type X, the error handler must be called, so you have to declare a static member of a new_handler type in the class. Then the class x looks like this: class x {public: static new_handler set_new_handler (new_handler p); static void * operator new (size_t size); private: static new_handler currenthandler;}; class static member must define outside of the class. Because I want to borrow the default initialization value of the static object, I don't start to initialize when defined x :: currenthandler. New_handler x :: currenthandler; // Default Settings CurrentHandler is 0 (ie Null) set_new_handler function saves any pointer to it and returns any pointer to which it is called before.
This is the standard version of set_new_handler made:. New_handler X :: set_new_handler (new_handler p) {new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler;} Finally, take a look at the operator new X made of: 1 call standard set_new_handler Function, input parameter is an error handler. This makes X's New-Handler function becomes a global new-handler function. Note that in the following code, the "::" symbol explicitly references the STD space (the standard set_new_handler function exists in the STD space). 2. Call the global operator new allocation of memory. If the first assignment fails, the global Operator New will call the X-Handler because it is just (see 1.) is installed as a global new-handler. If the global Operator New is finally failed to be assigned to memory, it throws std :: bad_alloc exception, X's Operator New will capture it. X of the Operator New then restores the originally replaced global new-handler function, and finally returns an exception. 3. Assume that the global operator new is allocated in memory, and the Operator New will call the criteria set_new_handler again to restore the original global error handling function. Finally, returns a pointer to the successful memory. C is doing this: void * x :: operator new (size_t size) {new_handler globalhandler = // Install x NEW_HANDLER std :: set_new_handler (CurrentHandler); void * memory; try {// Try allocation memory Memory = :: Operator new (size);} catch (std :: bad_alloc &) {// Restore old new_handler std :: set_new_handler (globalhandler); throw; // throw anomalous} std: set_new_handler (globalhandler); // Restore old New_handler returnemory;} If you repeat the std :: set_new_handler on the above, you can see the Terms M9 to remove them.
The memory allocation processing function using class X is approximately as follows: void NomoreMemory (); // x Object assigns the // new_new_handler (NomoreMemory) of the // x :: set_new_handler (NomoreMemory); // 把 omorememory; // X // new-handling function x * px1 = new x; //, such as memory allocation, // call nomorememoryString * ps = new string; // If the memory allocation failed, // Call the global new-handling function // x :: set_new_handler (0); // Set X NEW-handling function is empty // x * px2 = new x; // If the memory allocation fails, immediately throw an exception / (Class X No new-handling function) / / You will notice, process the above similar case, if you don't consider the class, the code is the same, this is very I naturally think that you can reuse them elsewhere. As explained in Terms 41, inheritance and templates can be used to design reusable code. Here, we combine both methods to meet your requirements. As long as you create a base class of "Mixin-style), this base class allows subclasses to inherit its specific function - here is the function of establishing a class of New-handler. The reason for designing a base class is to allow all subclasses to inherit the set_new_handler and Operator New feature, and the design template is to make each subclass have different CurrentHandler data members. This sounds complicated, but you will see the code is actually very familiar. The difference is that it can now be reused by any class.
template
The C Standards Committee does not want to give up the code that has followed the 0 specification, so they provide additional form Operator New (and Operator New [] - see Terms 8) to continue to provide the return 0 function. These forms are called "no throw" because they have not used a throw, but in the use of Nothrow objects in the entry point of New: Class Widget {...}; widget * pw1 = new widget; // Assign Failure throws std :: bad_alloc ingif (pw1 == 0) ... // This check must fail Widget * PW2 = new (notHrow) widget; // If the assignment fails to return 0iF (PW2 == 0) ... / / This check may succeed whether it is "Regular" (ie, throwing anomalies) New or "no thrown" form, it is important to prepare for memory allocation failure. The easiest way is to use SET_NEW_HANDLER because it is useful for both forms. Terpsis 8. When writing Operator New and Operator Delete, you should follow the regular yourself to override Operator New (Terms 10 explains why sometimes to rewrite it), it is important to be the behavior provided by the function and the system default Operator New consistent . It is actually what is also: there is a correct return value; the available memory is not enough to call the error handler (see Terms 7); process the 0 byte memory request. In addition, it is necessary to avoid accidentally hidden standard forms, but this is the topic of clause 9. The part of the return value is simple. If the memory allocation request is successful, return a pointer to the memory; if it fails, follow the provisions of the clause 7 throw a std :: Bad_alloc type exception. But things are not so simple. Because the Operator New actually tries to assign memory at a time, it is necessary to call an error handler after each failure, and it is also desirable that the error handler can find ways to release the memory. The Operator New throws an exception if the pointer to the error handler is empty. In addition, the C standard requires that the Operator New is also returned to a legitimate pointer even when the 0 byte memory is requested. (In fact, this sounds that sounds weird have brought simple to the C language. This, the pseudo code of the Operator New in the form of non-class does look like this: void * operator new (size_t size) / / Operator New may have other parameters {if (size == 0) {// When processing 0 byte request, size = 1; // Treat it as 1 byte request} While (1) {assignment Size byte memory; if (assignment success) return (pointing to memory pointer); // Distribution is unsuccessful, find the current error handler new_handler globalhandler = set_new_handler (0); set_new_handler (GlobalHandler); if (GlobalHandler) (* GlobalHandler) (); Else throw std :: Bad_alloc ();}} The trick to process the zero-byte request is to process it as a request byte.
This looks very blame, but simple, legal, effective. Moreover, how long will you encounter a zero-byte request? You will be strange why the error processing function is set to 0 after the pseudo code above. This is because there is no way to get a pointer to the error handling function, so you must find it by calling set_new_handler. The way is very stupid but is also effective. Terms 7 mentioned that the Operator New includes an infinite loop. The above code clearly shows this point --While (1) will result in an infinite loop. The only way to jump out the loop is that the memory allocation is successful or the error handler completed one of the events described in Terms 7: Get more available memory; installed a new new-handler (error handler); unload New-handler; throw a std :: Bad_alloc or its derived type of exception; or return fails. Now I understand why new-handler must do one of these work. If not, the loop inside Operator New will not end. Many people don't realize that Operator New often inherits by subclasses. This can lead to certain complexity. In the pseudo code above, the function will be assigned the SIZE byte memory (unless size is 0). SIZE is important because it is the parameter passed to the function. However, most of the Operator New (including Terms 10) written for classes is only designed for a particular class, not for all classes, nor for all of its subclass. This means that for a class X's Operator New, the behavior inside the function involves the overall size of the object, is accurate SIZEOF (X): Will not be large. However, due to the existence of inheritance, the Operator New in the base class may be called to allocate memory for a sub-class object: class base {public: static void * operator new; ...}; class derived: public Base / / Derived class does not declares operation new {...}; // derived * p = new derived; // call Base :: Operator new If the base class Operator New does not want to spend this situation - this situation There is little possibility - the easiest way is to transfer this "wrong" memory allocation request to the standard operator new to process, like this: void * base :: operator new (size_t size) {IF Size! = sizeof (base)) // If the number "error", let the standard operator new return :: Operator new (size); // deal with this request // ... / / Otherwise handling this request} "stop! "I heard you is calling," You forgot to check a situation although it is unreasonable but may have problems - Size may be zero! "Yes, I didn't check, but please call again next time. Don't be such a literary thing. :) But actually check or do it, but integrated into the Size! = SizeOf (Base) statement. The C standard is very weird, one of which is to specify that the independent (free "class is a non-zero value. So SizeOf (Base) can never be zero (even if the base class is not a member), if size is zero, the request will go to :: Operator New, which is processed in a reasonable way.
(Interestingly, if Base is not an independent class, sizeof (base) may be zero, detailed see "My Article on Counting Objects"). If you want to control the memory allocation based on the class-based array, you must implement the array of Operator New. ). When writing Operator New [], you should remember that you face "original" memory, you cannot do anything about objects that don't exist in arrays. In fact, you don't even know how many objects in the array, because I don't know how big each object is. The Operator New [] of the base class is used to allocate memory as an array of sub-objects in the manner, while sub-class objects are often larger than base categories. So, I can't think of that the size of each object in Base :: Operator New [] is sizeof (base), that is, the number of objects in the array is not necessarily (request byte) / sizeof (base). See the Terms M8 for details on Operator New []. Override Operator New (and Operator New []) All regular regulations are these. For Operator Delete (and its partner Operator Delete []), the situation is simpler. What you have to remember is that C guarantees that the empty pointer is always safe, so you have to fully apply this guarantee. Here is the pseudode code of the Operator delete in the form of a non-class member: void Operator delete (void * rawMemory) {if (RawMemory == 0) Return; // If the pointer is empty, return // Release RAWMEMORY point to the memory; return;} The class member version of the function is also simple, but it must also check the size of the object being deleted. Assuming the class of Operator New forwards the "Error" size request to :: Operator New, then you must transfer the "error" size delete request :: Operator Delete: Class Base {//, the same, just 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 empty pointer IF (size! = sizeof (base)) {// If the size "error", :: operator delete (RawMemory); // Let the standard operator to process the request Return } Release points to RawMemory's memory; Return;}, it is not so troublesome about Operator New and Operator Delete (and their array form), it is important to follow it. As long as the memory allocation supports the New-Handler function and correctly handles the zero memory request; if the memory release program handles the empty pointer, then there is nothing else to do.
As for adding inheritance support in the function of the class member version, it will soon be completed. Terms 9. Avoid hidden standard forms of New Because the name of the internal range declaration hides the same name of the external scope, the member function of the class of the same name, respectively, the same name, respectively, the class of the class Will hide the global function: void f (); // global function class x {public: void f (); // member function}; x x; f (); // call fx.f (); // call X :: f This will not be surprising, nor will it cause confusion because there is always different grammar forms when calling global functions and member functions. However, if you add an Operator New function with multiple parameters in the class, it is possible to be surprised. Class X {public: void f (); // Operator New parameter specifies a // new-hand (error handling) function static void * Operator new (size_t size, new_handler p);}; void specialerrorhandler (); / / Define in other places x * px1 = new (specialerrorhandler) x; // call x :: operator newx * px2 = new x; // error! Defined a function called "Operator New" in the class If you don't deliberately prevent access to standard New. The clause 50 explains why this is, here we are more concerned about how to think of a way to avoid this problem. One way is to write an Operator New that supports standard New calls in the class, and it is the same thing as standard New. This can be encapsulated with an efficient inline function. Class x {public: void f (); static void * operator new; static void * Operator new (size_t size) {return :: operator new (size);}}; x * px1 = new (Speciaalerrorhandler) x; // Call x :: operator // new (size_t, new_handler) x * px2 = new x; // call x :: operator // new (size_t) Another method is to increase to each The parameter of Operator New provides a default (see clause 24): class x {public: void f (); static void * operator new (size_t size, // p default is 0 new_handler p = 0); //} ; X * px1 = new (SpecialerRralhandler) x; // correct x * px2 = new x; // correctly, no matter which method, if you want to customize new features in the "standard" form, you only need to rewrite this function. The call can use new features after recoiling the link.
Terms 10. If you write Operator New, you should write Operator Delete to let's take a look at this 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 a class representing aircraft: class AirPlane only contains a pointer, which pointing is the actual description of the aircraft object (this technology will be described in terms 34): Class Airplanerep {...}; // Represents a plane object / / class airplane {public: ... private: airplanerep * rep; // Point 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 function will implicitly contain 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 memory block that looks like this: Pa -> AirPlane object memory is like a memory block like this: PA -> Memory Block Size Data AirPlane object memory For a small object like AirPlane, these additional data information will double the memory required by dynamically assigning objects (especially without virtual functions in the class) when). 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; // for being used Object airplane * next; // is used to use (in the free-linked list) object}; // class constant, specify how many // airplane objects in a large memory block, initialize Static Consta INT block_size; static airplane * headoffreeelist;}; the above code added several declarations: an Operator new function, a combination (make the same space in 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) {// Turn the "Error" size request to :: operator new () processing; // See Terms 8 IF (Size! = SIZEOF (AirPlane)) Return :: Operator New (size); AirPlane * P = // P Points the headoffreelist; // // P If it is legally, the header is moved to its next element /// If (p) headoffreelist = p-> next; Else {// Free linked list is empty, assign a large memory block, / / can accommodate the Block_Size Airplane object AirPlane * newblock = static_cast 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: AirPlane * pa = new airplane; // call // airplane :: operator new ... delete pa; // Call :: Operator delete Read this code, if you The ears will hear the aircraft crashed into the sound of burning, 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 it is going to follow this provision, see the Sidebar on Placement section of Article On Counting Objects) Thus, the continuation design The AirPlane class is as follows: Class Airplane {//, the same as front, but add a public : // Operator Delete Declaration ... static void Operator Delete (Void * deadObject, size_t size);}; // Passing to Operator Delete is a memory block, if // is correct, add free memory block The front // void airplane :: Operator Delete (Void * deadObject, size_t size) {if (deadObject == 0) Return; // See Terms 8 IF (Size! = SizeOf (AirPlane)) {// See Terms 8 :: operator delete (deadObject); return;} Airplane * carcass = static_cast Interestingly, if the object to be deleted is inherited from a class without a false argument function, the size_t value that passes the Operator Delete 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 Returned memory blocks are automatically released when not being used, but this will not do this, this has two reasons: the first reason and your custom memory management The original intent is related. 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 a large block memory, // Generate a free linked list, wait for the delete pa; // memory block empty; // Release it PA = New Airplane; // Get a large block memory again, // Generate a free linked list, wait for the delete pa; // The memory block is empty again, // Release ... // You have an idea ... Return 0;} The 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 // a dispenser void * alloc (SIZE_T N) for the size of N, / / Follow the Operator New General of the Terms 8 Void free (void * p, size_t n); // returns the memory fingers to the memory pool; // Follow the Operator Delete regular ~ pool (); // release of the memory pool}; this class Support for the creation of the Pool object, perform 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.