⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 5resource.html

📁 Visual C++ has been one of most effective tool for the large industrial applications. This book is t
💻 HTML
📖 第 1 页 / 共 5 页
字号:
<HTML>
<HEAD>

<TITLE>Resource Management</TITLE>
    <meta  name="description" content="Safe management of resources in C++">
    <meta name="keywords" content="resource, management, new, delete, smart pointer, auto_ptr">
<link rel="stylesheet" href="rs.css" tppabs="http://www.relisoft.com/book/rs.css">
</HEAD>
<BODY background="margin.gif" tppabs="http://www.relisoft.com/book/images/margin.gif" bgcolor="#ffffe0">

<!-- Main Table -->
<table cellpadding="6">
    <tr>
    <td width="78">&nbsp;</td>
    <td>

<h2>Code Review 5: Resource Management</h2>
</td></tr>
<tr>
<td class=margin valign=top>
<a href="calc5.zip" tppabs="http://www.relisoft.com/book/tech/source/calc5.zip">
<img src="brace.gif" tppabs="http://www.relisoft.com/book/images/brace.gif" width=16 height=16 border=1 alt="Download!"><br>source</a>
</td>
<td>
<P>There's one important flaw in our program. It assumes that memory allocation never fails. This assumption is blatantly false. Granted, in a virtual-memory system allocation limits are rather high. In fact, the computer is not even limited by the size of its physical memory. The only way memory allocation may fail is if you run out of space in the swap file on your disk. Now, that's not something unheard of in the world of huge applications and multimedia files. An industrial-strength program should protect itself from it. The question is, how?

What should a program do when the call to <var>new</var> fails? First of all, if we don't do anything about it (as indeed we didn't), the system will terminate us with extreme prejudice. That's totally unacceptable!

<p>The next, slightly more acceptable thing would be to print a good-bye message and exit. For a program such as our calculator that might actually be the correct thing. It's not like the user runs the risk of losing hours of work when the calculator unexpectedly exits. 

<p>There are a few different ways to globally intercept a memory failure. One is to catch the <var>bad_alloc</var> exception--more about it in a moment--another, to create your own "new handler" and register it with the runtime at the beginning of your program. The function <var>set_new_handler</var> is defined in the header <span class="file">&lt;new&gt;</span>. It accepts a pointer to a function that's to be called in case <var>new</var> fails. Here's an example of a user-defined new handler:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>void NewHandler ()
{
    cerr &lt;&lt; "Out of memory\n";
    exit (1);
    // not reached
    return 0;
}</pre>
    </td></tr>
</table>
<!-- End Code -->
<p>It displays a message end exits by calling a special function, <var>exit</var> from <span class="file">&lt;cstdlib&gt;</span>.
<p>This is how you register your handler--preferably at the beginning of main.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>set_new_handler (&amp;NewHandler);</pre>
    </td></tr>
</table>
<!-- End Code -->

<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10>&nbsp;</td>
<td bgcolor="#cccccc" class=sidebar>
If you have a non-standard-compliant compiler you might have to modify this code. For instance, in Microsoft VC++ 6.0 there is a function called <var>_set_new_handler</var> that serves a similar purpose. Its declaration is in <span class="file">&ltnew.h&gt;</span>. You'll also have to change the signature of the function you pass to it--to <var>int NewHandler (size_t size)</var>.
</td></table>
<!-- End Sidebar -->


<P>Although in many cases such a solution is perfectly acceptable, most commercial programs require more sophisticated techniques (and maybe, at some point in the future, will actually apply them!). Just try to imagine a word processor (or a compiler) suddenly exiting without giving you a chance to save your work. Sounds familiar?!

<p>What happens when memory allocation fails? In standard C++, operator <var>new</var> generates an exception. So let's talk about exceptions.

<h3>Exceptions</h3>

<P>in C++ we deal with exceptional failures using exceptions. Essentially it is up to the programmer to decide what is exceptional and what isn't. A memory allocation failure is considered exceptional--so is a failed disk read or write. On the other hand "file not found" or "end of file" are considered normal events. When an exceptional condition occurs, the program can throw an exception. A <var>throw</var> bypasses the normal flow of control and lands us in the <var>catch</var> part of the nearest enclosing <var>try</var>/<var>catch</var> block. So <var>throw</var> really behaves like a non-local <var>goto</var> (non-local meaning that the control can jump across function calls).

<P>We can best understand the use of exceptions using the calculator as an example. The only exceptional condition that we can anticipate there is the memory allocation failure. As I mentioned before, when <var>new</var> is not able to allocate the requested memory, it throws an exception of the type <var>bad_alloc</var>.

<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10>&nbsp;</td>
<td bgcolor="#cccccc" class=sidebar>
Again, this is true in the ideal world. At the time of this writing most compilers still haven't caught up with the standard. But don't despair, you can work around this problem by providing your own <var>new</var>-failure handler that throws an exception.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>class bad_alloc {};

int NewHandler (size_t size)
{
    throw bad_alloc ();
    return 0;
}</pre>

    </td></tr>
</table>
<!-- End Code -->
Then call <var>_set_new_handler (&amp;NewHandler);</var> first thing in main. (Notice: the handler creates an object of the type <var>bad_alloc</var>, by calling its default constructor, which does nothing. The type <var>bad_alloc</var> itself is a dummy--we define it only to be able to distinguish between different types of exceptions.)

</td></table>
<!-- End Sidebar -->


<P>If we don't catch this exception, the program will terminate in a rather unpleasant way. So we better catch it. The program is prepared to catch an exception as soon as the flow of control enters a <var>try</var>/<var>catch</var> block. So let's open our <var>try</var> block in <var>main</var> before we construct the symbol table (whose constructor does the first memory allocations). At the end of the <var>try</var> block one must start a <var>catch</var> block that does the actual catching of the exception.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>int main ()
{
    // only if your new doesn't throw by default
    _set_new_handler (&amp;NewHandler);

    try
    {
        char buf [maxBuf+1];
        Status status;
        SymbolTable symTab;
        Function::Table funTab (symTab);
        Store store (symTab);
        ...
    }
    catch (bad_alloc)
    {
        cerr &lt;&lt; "Out of memory!\n";
    }
    catch (...)
    {
        cerr &lt;&lt; "Internal error\n";
    }
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>Here we have two catch blocks, one after another. The first will catch only one specific type of exception--the <var>bad_alloc</var> one--the second , with the ellipsis, will catch all the rest. The "internal error" may be a general protection fault or division by zero in our program. Not that we plan on allowing such bugs! But it's always better to display a message end exit than to let the system kill us and display its own message.

<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
Always enclose the main body of your program within a try/catch block.
    </td></tr>
</table>
<!-- End Definition -->


<P>Figure 1 illustrates the flow of control after an exception is thrown during the allocation of the <var>SumNode</var> inside <var>Parser::Expr</var> that was called by <var>Parser::Parse</var> called from within the <var>try</var> block inside <var>main</var>. 

<p><img src="Image58.gif" tppabs="http://www.relisoft.com/book/tech/images/Image58.gif" width=318 height=282 border=0 alt="Catching a bad_alloc exception">
<p>Figure 1. Throw and catch across multiple function calls.

<p>The use of exceptions solves the problem of error propagation (see sidebar) by bypassing the normal flow of control.

<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10>&nbsp;</td>
<td bgcolor="#cccccc" class=sidebar>
<center>Error propagation.</center>
<p>In the old times, memory allocation failure used to be reported by <var>new</var> returning a null pointer. A programmer who wanted to protect his program from catastrophic failures had to test the result of every allocation. Not only was it tedious and error-prone, but it wasn't immediately clear what to do when such a failure occured. All a low level routine could do is to stop executing and return an error code to the calling routine. That, of course, put the burden of checking for error returns on the caller. In most cases the caller had little choice but to pass the same error code to its caller, and so on. Eventually, at some higher level--maybe even in main--somebody had the right context to do something about the problem, e.g., report it to the user, try to salvage his work, and maybe exit. Not only was such methodology error prone (forgetting to check for error returns), but it contributed dangerously to the obfuscation of the main flow of control. By the way, this problem applies to all kinds of errors, not only memory allocation failures.
</td></table>
<!-- End Sidebar -->

<p>
<!-- Sidebar -->
<table width=100% border=0 cellpadding=5><tr>
<td width=10>
<td bgcolor="#cccccc" class=sidebar>
<center>Exceptions and Debugging</center>
<p>Since exceptions should not happen during normal program execution, it would make sense for your debugger to stop whenever an exception is thrown. This is particularly important when you have a catch-all clause in your program. It will not only catch those exceptions that are explicitly thrown--it will catch hardware exceptions caused by accessing a null pointer or division by zero. Those definitely require debugging.

<p>Unfortunately, not all debuggers stop automatically on exceptions. For instance, Microsoft Visual C++ defaults to "stop only if not caught". You should change it. The appropriate dialog is available from the <i>Debug</i> menu (which is displayed only during debugging). Select the <i>Exceptions</i>  item and change the action on all exceptions to "stop always."
</table>
<!-- End Sidebar -->


<h3>Stack Unwinding</h3>

<p>Figuring out how to propagate an error is the easy part. The real problem is the cleanup. You keep allocating memory in your program and, hopefully, you remember to deallocate it when it's no longer needed. If this weren't hard enough within the normal flow of control, now you have to remember to deallocate this memory when propagating an error.

<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10>&nbsp;</td>
<td bgcolor="#cccccc" class=sidebar>
Many a scheme was invented to deal with the cleanup during traditional error propagation. Since in most cases memory resources were freed at the end of procedures, one method was to use <var>goto</var>s to jump there whenever an error was detected. A typical procedure might look something like this:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>ERRCODE foo ()
{
    ERRCODE status = errNoError;
    Type1 * p1 = 0;
    Type2 * p2 = 0;
    <b>p1 = new Type1;</b>
    if (p1 == 0)
    {
        status = errOutOfMemory;
        goto Cleanup;
    }
    status = <b>call1 (p1);</b>
    if (status != errNoError)
        goto Cleanup;
    <b>p2 = new Type2;</b>
    if (p2 == 0)
    {
        status = errOutOfMemory;
        goto Cleanup;
    }
    status = <b>call2 (p2);</b>
    if (status != errNoError)
        goto Cleanup;
    // more of the same
Cleanup:
    <b>delete p2;</b>
    <b>delete p1;</b>
    return status;
}</pre>
    </td></tr>
</table>
<!-- End Code -->
All this code just to allocate and destroy two objects and make two function calls (the code not dealing with error detection and propagation has been highlighted). If you think I'm exaggerating, don't! I've seen files after files of commercial code written in this style. Incidentally, adding a <var>return</var> statement in the middle of such a procedure will totally invalidate the whole cleanup logic.

</td></table>
<!-- End Sidebar -->

<p>Wait, it gets worse before it gets better! When you use exceptions to propagate errors, you don't get a chance to clean-up your allocations unless you put <var>try</var>/<var>catch</var> pairs in every procedure. Or so it seems. The straightforward translation of the sidebar example would look something like this:

<!-- Code -->
<table width="100%" cellspacing=10><tr>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -