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

📄 5resource.html

📁 Visual C++ has been one of most effective tool for the large industrial applications. This book is t
💻 HTML
📖 第 1 页 / 共 5 页
字号:
    <td class=codeTable>
<pre>void foo ()
{
    Type1 * p1 = 0;
    Type2 * p2 = 0;
    try
    {
        <b>p1 = new Type1;</b>
        <b>call1 (p1);</b>
        <b>p2 = new Type2;</b>
        <b>status = call2 (p2);</b>
        // more of the same
    }
    catch (...)
    {
        delete p2;
        delete p1;
        throw;
    }
    <b>delete p2;</b>
    <b>delete p1;</b>
}</pre>
    </td></tr>
</table>
<!-- End Code -->
<p>This code looks cleaner but it's still very error-prone. Pointers are zeroed in the beginning of the procedure, assigned to in the middle and deallocated at the end (notice the ugly, but necessary, repetition of the two <var>delete</var> statements). Plenty of room for mistakes. By the way, the <var>throw</var> with no argument re-throws the same exceptions that was caught in the enclosing <var>catch</var> block.

<p>At this point you might feel desperate enough to start thinking of switching to Java or some other language that offers automatic garbage collection. Before you do, read on.

<P>There is a mechanism in C++ that just begs to be used for cleanup--the pairing of constructors and destructors of automatic objects. Once an object is constructed in some local scope, it is always automatically destroyed when the flow of control exits that scope. The important thing is that it really doesn't matter how the scope is exited: the flow of control may naturally reach the closing bracket, or it may jump out of the scope through a <var>goto</var>, a <var>break</var> or a <var>return</var> statement. Automatic objects are <b>always</b> destroyed. And yes--this is very important--when the scope is exited through an exception, automatic objects are cleaned up too! Look again at Figure 1--all the stack based objects in all the nested scopes between the outer <var>try</var> block and the inner <var>throw</var> block are automatically destroyed--their destructors are executed in the inverse order of construction.


<p>I'll show you in a moment how to harness this powerful mechanism, called <var>stack unwinding</var>, to do all the cleanup for us. But first let's consider a few tricky situations.

<p>For instance, what happens when an exception is thrown during construction? Imagine an object with two embeddings. In its constructor the two embedded objects are constructed, either implicitly or explicitly (through the preamble). Now suppose that the first embedding has been fully constructed, but the constructor of the second embedding throws an exception. What do you think should happen? Shouldn't the first embedding be destroyed in the process of unwinding? Right on! That's exactly what happens--the destructors of all fully constructed sub-objects are executed during unwinding.

<P>What if the object is dynamically allocated using <var>new</var> and its constructor throws an exception? Shouldn't the memory allocated for this object be freed? Right again! That's exactly what happens--the memory allocated by <var>new</var> is automatically freed if the constructor throws an exception.
<P>What if the heap object has sub-objects and the constructor of the nth embedding throws an exception? Shouldn't the n-1 sub-objects be destroyed and the memory freed? You guessed it! That's exactly what happens.
<P>Remember the rules of object construction? First the base class is constructed, then all the embedded objects are constructed, finally the body of the constructor is executed. If at any stage of the construction an exception is thrown, the whole process is reversed and all the completely constructed parts of the object are destroyed (in the reverse order of construction).
<P>What if an exception is thrown during the construction of the nth element of an array. Shouldn't all the n-1 fully constructed elements be destroyed? Certainly! That's exactly what happens. It all just works!
<P>These rules apply no matter whether the object (array) is allocated on the stack or on the heap. The only difference is that, once a heap object is <i>fully constructed</i>, it (and its parts) will not take part in the process of stack unwinding.

<p>There is only one situation when stack unwinding might not do the right thing. It's when an exception is thrown from within a destructor. So don't even think of allocating some scratch memory, saving a file to disk, or doing any of the many error-prone actions in a destructor. A distructor is supposed to quietly clean up.

<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
Never perform any action that could throw an exception inside a destructor.
    </td></tr>
</table>
<!-- End Definition -->



<P>Now take our symbol table. 

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>class SymbolTable
{
...
private:
    HTable            _htab;
    std::vector&lt;int&gt;  _aOffStr;
    StringBuffer      _bufStr;
};</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>It has three embedded objects, all with nontrivial constructors. The hash table allocates an array of lists, the vector of offsets might allocate some initial storage and the string buffer allocates a buffer for strings. Any one of these allocations may fail and throw an exception. 
<P>If the constructor of the hash table fails, there is no cleanup (unless the whole <var>SymbolTable</var> object was allocated using <var>new</var>--in that case the memory for it is automatically freed). If the hash table has been successfully constructed but the vector fails, the destructor of the hash table is executed during unwinding. Finally, if the exception occurs in the constructor of the string buffer, the destructors of the vector and the hash table are called (in that order).

<P>The constructor of the hash table itself could have some potentially interesting modes of failure. 
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>HTable::HTable (int size)
    : _size (size)
{
    _aList = new List&lt;int&gt; [size];
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>Assume for a moment that the constructor of <var>List&lt;int&gt;</var> is nontrivial (for example, suppose that it allocates memory). The array of 65 (<var>cSymInit</var> + 1) lists is constructed in our calculator. Imagine that the constructor of the 8th list throws an exception. The destructors of the first seven entries of the array will automatically be called, after which the memory for the whole array will be freed. If, on the other hand, all 65 constructors succeed, the construction of the hash table will be successful and the only way of getting rid of the array will be through the destructor of the hash table. 
<P>But since the hash table is an embedded object inside the symbol table, and the symbol table is an automatic object inside the <var>try</var> block in main, its destruction is guaranteed. Lucky accident? Let's have another look at main.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>int main ()
{
    _set_new_handler (&amp;NewHandler);
    try
    {
        ...
        SymbolTable symTab;
        Function::Table funTab (symTab);
        Store store (symTab);
        ...
        {
            Scanner scanner (buf);
            Parser  parser (scanner, store, funTab, symTab);
            ...
        }
    }
    catch (...)
    {
        ...
    }
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>It looks like all the resources associated with the symbol table, the function table, the store, the scanner and the parser are by design guaranteed to be released. We'll see in a moment that this is indeed the case.

<h3>Resources </h3>

<P>So far we've been dealing with only one kind of resource--memory. Memory is acquired from the runtime system by calling <var>new</var>, and released by calling <var>delete</var>. There are many other kinds of resources: file handles, semaphores, reference counts, etc. They all have one thing in common--they have to be acquired and released. Hence the definition:

<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
A resource is something that can be acquired and released.     </td></tr>
</table>
<!-- End Definition -->

<P>The acquiring part is easy--it's the releasing that's the problem. An unreleased resource, or resource leak, can be anywhere from annoying (the program uses more memory than is necessary) to fatal (the program deadlocks because of an unreleased semaphore). 
<P>We can guarantee the matching of the acquisition and the release of resources by following the simple <b><i>First Rule of Acquisition</i></b>:
<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
Acquire resources in constructors, release resources in matching destructors.
</td></tr>
</table>
<!-- End Definition -->

<P>In particular, the acquisition of memory through <var>new</var> should be only done in a constructor, with the matching <var>delete</var> in the destructor. You'll find such matching pairs in the constructors and destructors of <var>FunctionTable</var>, <var>HTable</var> (and <var>std::vector&lt;T&gt;</var>, if you look it up in <span class="file">&lt;vector&gt;</span>). (The other occurrences of <var>new</var> in our program will be explained later.)

<P>However, if you look back at the symbol table right before we separated out the string buffer, you'll see something rather disturbing:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>SymbolTable::SymbolTable (int size)
    : _size (size), _curId (0), _curStrOff (0), _htab (size + 1)
{
    _offStr = new int [size];
    _bufSize = size * 10;
    _strBuf = new char [_bufSize];
}

SymbolTable::~SymbolTable ()
{
    delete []_offStr;
    delete []_strBuf;
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>The <b><i>First Rule of Acquisition</i></b> is being followed all right, but the code is not exception safe. Consider what would happen if the second allocation (of <var>_strBuf</var>) failed. The first allocation of <var>_offStr</var> would have never been freed.
<P>To prevent such situations, when programming with exceptions, one should keep in mind the following corollary to the First Rule of Acquisition:
<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
Allocation of resources should either be the last thing in the constructor or be followed by exception safe code.    </td></tr>
</table>
<!-- End Definition -->

<P>If more than one resource needs to be allocated in a constructor, one should create appropriate sub-objects to hold these resources. They will be automatically released even if the constructor fails before completion.

<p>As you can see, there are many different arguments that lead to the same conclusion--it makes sense to separate sub-objects! We can even enhance our rule of thumb that suggested the separation of sub-object when a class had too many data members: Create sub-objects when more than one data member points to a resource. In our symbol table it was the introduction of the following two sub-objects that did the job.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>    std::vector&lt;int&gt;  _aOffStr;
    StringBuffer      _bufStr;</pre>
    </td></tr>
</table>
<!-- End Code -->

<h3>Ownership of Resources</h3>

<P>An object or a block of code owns a resource if it is responsible for its release. The concept of <i>ownership of resources</i> is central to the resource management  paradigm. The owns-a relationship between objects parallels the more traditional is-a (inheritance) and has-a (embedding) relationships. There are several forms of ownership.
<P>A block of code owns all the automatic objects defined in its scope. The job of releasing these resources (calling the destructors) is fully automated.
<P>Another form of ownership is by embedding. An object owns all the objects embedded in it. The job of releasing these resources is automated as well (the destructor of the big object calls the destructors for the embedded objects).
<P>In both cases the lifetime of the resource is limited to the lifetime of the embedding entity: the activation of a given scope or the lifetime of an embedding object. Because of that, the compiler can fully automate destructor calls.
<P>The same cannot be said for dynamically allocated objects that are accessed through pointers. During their lifetimes, pointers can point to a sequence of resources. Also, several pointers can point to the same resource. The release of such resources is not automated. If the object is being passed from pointer to pointer and the ownership relations cannot easily be traced, the programmer is usually in for a trouble. The symptoms may be various: uninitialized pointers, memory leaks, double deletions, etc.
<P>Imposing the <b><i>First Rule of Acquisition</i></b> clarifies the ownership relationships and guarantees the absence of most errors of the types we have just listed. A block of code can only own its automatic objects--no naked pointers should be allowed there. An object can own other objects by embedding as well as through pointers. In the latter case though, the acquisition of the resources (initialization of pointers) has to take place in the object's constructor, and their release in the destructor. Constructors are the only places where a naked pointer may appear and then only for a very short time.

<P>Notice that if all resources have owners, they are guaranteed to be released. The objects that own resources are either defined in the global scope, or in a local scope or are owned by other objects--through embeddings or through  dynamic allocation in constructors. Global objects are destroyed after exit from main, local objects are destroyed upon exit from their scope, embeddings are either destroyed when the enclosing objects are destroyed, or when the enclosing constructor is aborted through an exception. Since all the owners are eventually destroyed, or the resources are eventually freed.

<h3>Access to Resources</h3>

<P>A block of code or an object may operate on a resource without owning it, that is, without being responsible for its release. When granting or passing access, as opposed to ownership, one should try to use references whenever possible. We have already learned that the has-access-to relationship is best expressed using references. A reference cannot express ownership because an object cannot be deleted through a reference (at least not without some trickery). 
<P>There are rare cases when using a reference is awkward or impossible. In such cases one can use a <b>weak pointer</b>--a pointer that doesn't express ownership--only access. In C++ there is no syntactic difference between strong and week pointers, so this distinction should be made by the appropriate commenting of the code. 
<P>We will graphically represent the has-access-to relationship (either through a reference or through a weak pointer) by a dotted arrow, as in Figure 2.

<p><img src="Image59.gif" tppabs="http://www.relisoft.com/book/tech/images/Image59.gif" width=192 height=120 border=0 alt="Has-access-to relationship">

<P>Figure 2. Graphical representation of the has-access-to relationship between <var>VarNode</var> and <var>Store</var>.

<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10>&nbsp;</td>
<td bgcolor="#cccccc" class=sidebar>
If access to a resource is reference-counted, the ownership rules apply to the reference count itself. Reference count should be acquired (incremented) in a constructor and released (decremented) in a destructor. The object itself may be passed around using a reference or a pointer, depending on whether there is one distinguished owner, or the ownership is distributed (the last client to release the reference count, destroys the object).</td></table>
<!-- End Sidebar -->


<h3>Smart Pointers</h3>

<P>Our calculator was implemented with no regard for resource management. There are naked pointers all over the place, especially in the parser. Consider this fragment.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>do

⌨️ 快捷键说明

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