📄 ei10.htm
字号:
<UL><PRE>
Airplane *p = // p is now a pointer to the
headOfFreeList; // head of the free list
</PRE>
</UL><A NAME="14179"></A>
<UL><PRE> // if p is valid, just move the list head to the
// next element in the free list
if (p)
headOfFreeList = p->next;
</PRE>
</UL><A NAME="14180"></A>
<UL><PRE> else {
// The free list is empty. Allocate a block of memory
// big enough to hold BLOCK_SIZE Airplane objects
Airplane *newBlock =
static_cast<Airplane*>(::operator new(BLOCK_SIZE *
sizeof(Airplane)));
</PRE>
</UL><A NAME="14183"></A>
<UL><PRE> // form a new free list by linking the memory chunks
// together; skip the zeroth element, because you'll
// return that to the caller of operator new
for (int i = 1; i < BLOCK_SIZE-1; ++i)
newBlock[i].next = &newBlock[i+1];
</PRE>
</UL><A NAME="14184"></A>
<UL><PRE> // terminate the linked list with a null pointer
newBlock[BLOCK_SIZE-1].next = 0;
</PRE>
</UL><A NAME="14185"></A>
<UL><PRE> // set p to front of list, headOfFreeList to
// chunk immediately following
p = newBlock;
headOfFreeList = &newBlock[1];
}
</PRE>
</UL><A NAME="14186"></A>
<UL><PRE> return p;
}
</PRE>
</UL><A NAME="14255"></A>
<P><A NAME="dingp15"></A>
If you've read <A HREF="./EI8_FR.HTM#120851" TARGET="_top">Item 8</A>, you know that when <CODE>operator</CODE> <CODE>new</CODE> can't satisfy a request for memory, it's supposed to perform a series of ritualistic steps involving new-handler functions and exceptions. There is no sign of such steps above. That's because this <CODE>operator</CODE> <CODE>new</CODE> gets all the memory it manages from <CODE>::operator</CODE> <CODE>new</CODE>. That means this <CODE>operator</CODE> <CODE>new</CODE> can fail only if <CODE>::operator</CODE> <CODE>new</CODE> does. But if <CODE>::operator</CODE> <CODE>new</CODE> fails, <I>it</I> must engage in the new-handling ritual (possibly culminating in the throwing of an exception), so there is no need for <CODE>Airplane</CODE>'s <CODE>operator</CODE> <CODE>new</CODE> to do it, too. In other words, the new-handler behavior is there, you just don't see it, because it's hidden inside <CODE>::operator</CODE> <CODE>new</CODE>.<SCRIPT>create_link(15);</SCRIPT>
</P>
<A NAME="14256"></A>
<P><A NAME="dingp16"></A>
<A NAME="p43"></A>Given this <CODE>operator</CODE> <CODE>new</CODE>, the only thing left to do is provide the obligatory definitions of <CODE>Airplane</CODE>'s static data <NOBR>members:<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<A NAME="14260"></A>
<UL><PRE>
Airplane *Airplane::headOfFreeList; // these definitions
// go in an implemen-
const int Airplane::BLOCK_SIZE = 512; // tation file, not
// a header file
</PRE>
</UL><A NAME="2023"></A>
<P><A NAME="dingp17"></A>
There's no need to explicitly set <CODE>headOfFreeList</CODE> to the null pointer, because static members are initialized to 0 by default. The value for <CODE>BLOCK_SIZE</CODE>, of course, determines the size of each memory block we get from <CODE>::operator</CODE> <CODE>new</CODE>.<SCRIPT>create_link(17);</SCRIPT>
</P>
<A NAME="2024"></A>
<P><A NAME="dingp18"></A>
This version of <CODE>operator</CODE> <CODE>new</CODE> will work just fine. Not only will it use a lot less memory for <CODE>Airplane</CODE> objects than the default <CODE>operator</CODE> <CODE>new</CODE>, it's also likely to be faster, possibly as much as two orders of magnitude faster. That shouldn't be surprising. After all, the general version of <CODE>operator</CODE> <CODE>new</CODE> has to cope with memory requests of different sizes, has to worry about internal and external fragmentation, etc., whereas your version of <CODE>operator</CODE> <CODE>new</CODE> just manipulates a couple of pointers in a linked list. It's easy to be fast when you don't have to be <NOBR>flexible.<SCRIPT>create_link(18);</SCRIPT>
</NOBR></P>
<A NAME="2025"></A>
<P><A NAME="dingp19"></A>
At long last we are in a position to discuss <CODE>operator</CODE> <CODE>delete</CODE>. Remember <CODE>operator</CODE> <CODE>delete</CODE>? This Item is <I>about</I> <CODE>operator</CODE> <CODE>delete</CODE>. As currently written, your <CODE>Airplane</CODE> class declares <CODE>operator</CODE> <CODE>new</CODE>, but it does not declare <CODE>operator</CODE> <CODE>delete</CODE>. Now consider what happens when a client writes the following, which is nothing if not eminently <NOBR>reasonable:<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<A NAME="2026"></A>
<UL><PRE>
Airplane *pa = new Airplane; // calls
// Airplane::operator new
...
</PRE>
</UL><A NAME="2027"></A>
<UL><PRE>
delete pa; // calls ::operator delete
</PRE>
</UL><A NAME="2028"></A>
<P><A NAME="dingp20"></A>
If you listen closely when you read this code, you can hear the sound of an airplane crashing and burning, with much weeping and wailing by the programmers who knew it. The problem is that <CODE>operator</CODE> <CODE>new</CODE> (the one defined in <CODE>Airplane</CODE>) returns a pointer to memory <I>without any header information</I>, but <CODE>operator</CODE> <CODE>delete</CODE> (the default, global one) assumes that the memory it's passed <I>does</I> contain header information! Surely this is a recipe for <NOBR>disaster.<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P>
<A NAME="2030"></A>
<P><A NAME="dingp21"></A>
This example illustrates the general rule: <CODE>operator</CODE> <CODE>new</CODE> and <CODE>operator</CODE> <CODE>delete</CODE> must be written in concert so that they share the same assumptions. If you're going to roll your own memory allocation routine, be sure to roll one for deallocation, too. (For another reason why you should follow this advice, turn to <A HREF="../MAGAZINE/CO_FRAME.HTM#sidebar" TARGET="_top">the sidebar on placement <CODE>new</CODE> and placement <CODE>delete</CODE></A> in my <A HREF="../MAGAZINE/CO_FRAME.HTM" TARGET="_top">article on counting objects</A> in <NOBR>C++.)<SCRIPT>create_link(21);</SCRIPT>
</NOBR></P>
<A NAME="2031"></A>
<P><A NAME="dingp22"></A>
Here's how you solve the problem with the <CODE>Airplane</CODE> <NOBR>class:<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="2032"></A>
<UL><PRE><A NAME="p44"></A>
class Airplane { // same as before, except there's
public: // now a decl. for operator delete
...
</PRE>
</UL><A NAME="14328"></A>
<UL><PRE>
static void operator delete(void *deadObject,
size_t size);
</PRE>
</UL><A NAME="2036"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="14310"></A>
<UL><PRE>// operator delete is passed a memory chunk, which,
// if it's the right size, is just added to the
// front of the list of free chunks
void Airplane::operator delete(void *deadObject,
size_t size)
{
if (deadObject == 0) return; // see <A HREF="./EI8_FR.HTM#120851" TARGET="_top">Item 8</A>
</PRE>
</UL><A NAME="14338"></A>
<UL><PRE>
if (size != sizeof(Airplane)) { // see <A HREF="./EI8_FR.HTM#120851" TARGET="_top">Item 8</A>
::operator delete(deadObject);
return;
}
</PRE>
</UL><A NAME="14314"></A>
<UL><PRE> Airplane *carcass =
static_cast<Airplane*>(deadObject);
</PRE>
</UL><A NAME="14315"></A>
<UL><PRE> carcass->next = headOfFreeList;
headOfFreeList = carcass;
}
</PRE>
</UL><A NAME="2038"></A>
<P><A NAME="dingp23"></A>
Because you were careful in <CODE>operator</CODE> <CODE>new</CODE> to ensure that calls of the "wrong" size were forwarded to the global <CODE>operator</CODE> <CODE>new</CODE> (see <A HREF="./EI8_FR.HTM#120851" TARGET="_top">Item 8</A>), you must demonstrate equal care in ensuring that such "improperly sized" objects are handled by the global version of <CODE>operator</CODE> <CODE>delete</CODE>. If you did not, you'd run into precisely the problem you have been laboring so arduously to avoid — a semantic mismatch between <CODE>new</CODE> and <CODE>delete</CODE>.<SCRIPT>create_link(23);</SCRIPT>
</P>
<A NAME="14828"></A>
<P><A NAME="dingp24"></A>
Interestingly, the <CODE>size_t</CODE> value C++ passes to <CODE>operator</CODE> <CODE>delete</CODE> may be <I>incorrect</I> if the object being deleted was derived from a base class lacking a virtual destructor. This is reason enough for making sure your base classes have virtual destructors, but <A HREF="./EI14_FR.HTM#223029" TARGET="_top">Item 14</A> describes a second, arguably better reason. For now, simply note that if you omit virtual destructors in base classes, <CODE>operator</CODE> <CODE>delete</CODE> functions may not work <NOBR>correctly.<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="14350"></A>
<P><A NAME="dingp25"></A>
All of which is well and good, but I can tell by the furrow in your brow that what you're really concerned about is the memory leak. With all the software development experience you bring to the table, there's no way you'd fail to notice that <CODE>Airplane</CODE>'s <CODE>operator</CODE> <CODE>new</CODE> calls <CODE>::operator</CODE> <CODE>new</CODE> to get big blocks of memory, but Airplane's <CODE>operator</CODE> <CODE>delete</CODE> <A NAME="p45"></A>fails to release those blocks.<A HREF="#14365"><sup>4</sup></A> <I>Memory leak! Memory leak!</I> I can almost hear the alarm bells going off in your <NOBR>head.<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<A NAME="14361"></A>
<P><A NAME="dingp26"></A>
Listen to me carefully: <I>there is no memory leak</I>.<SCRIPT>create_link(26);</SCRIPT>
</P>
<A NAME="14363"></A>
<P><A NAME="dingp27"></A>
A memory leak arises when memory is allocated, then all pointers to that memory are lost. Absent garbage collection or some other extralinguistic mechanism, such memory cannot be reclaimed. But this design has no memory leak, because it's never the case that all pointers to memory are lost. Each big block of memory is first broken down into <CODE>Airplane</CODE>-sized chunks, and these chunks are then placed on the free list. When clients call <CODE>Airplane::operator</CODE> <CODE>new</CODE>, chunks are removed from the free list, and clients receive pointers to them. When clients call <CODE>operator</CODE> <CODE>delete</CODE>, the chunks are put back on the free list. With this design, all memory chunks are either in use as <CODE>Airplane</CODE> objects (in which case it's the clients' responsibility to avoid leaking their memory) or are on the free list (in which case there's a pointer to the memory). There is no memory <NOBR>leak.<SCRIPT>create_link(27);</SCRIPT>
</NOBR></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -