First, let’s look at how C++ normally handles function calls and returns. C++ typically handles function calls by placing information on a stack (see Chapter 9, “Memory Models and Namespaces”). In particular, a program places the address of a calling function instruction (a return address) on the stack. When the called function completes, the program uses that address to determine where to continue with program execution. Also the function call places any function arguments on the stack, where they are treated as automatic variables. If the called function creates any new automatic variables, they, too, are added to the stack. If a called function calls another function, its information is added to the stack, and so on. When a function terminates, program execution passes to the address stored when the function was called, and the top of the stack is freed. Thus a function normally returns to the function that called it, with each function liberating its automatic variables as it terminates. If an automatic variable is a class object, then the class destructor, if any, is called.

Now suppose a function terminates via a thrown exception instead of via a return call. Again, the program frees memory from the stack. But instead of stopping at the first return address on the stack, the program continues freeing the stack until it reaches a return address that resides in a try block (see Figure 15.3). Control then passes to the exception handlers at the end of the block rather than to the first statement following the function call. This process is called unwinding the stack. One very important feature of the throw mechanism is that, just as with function returns, the class destructors are called for any automatic class objects on the stack. However, a function return just processes objects put on the stack by that function, whereas the throw statement processes objects put on the stack by the entire sequence of function calls between the try block and the throw. Without the unwinding-the-stack feature, a throw would leave destructors uncalled for automatic class objects placed on the stack by intermediate function calls.

Figure 15.3. throw versus return.

Listing 15.12 provides an example of unwinding the stack. In it, main() calls means(), which in turn calls hmean() and gmean(). The means() function, for the lack of anything better to do, calculates the mean of the arithmetic, harmonic, and geometric means. Both main() and means() create objects of the demo type (a babbling class that announces when its constructor and destructor are used) so that you can see what happens to those objects when exceptions are thrown. The try block in main() catches both bad_hmean and bad_gmean exceptions, and the try block in means() catches just the bad_hmean exception. This catch block has the following code:

catch (bad_hmean & bg) // start of catch block

{

    bg.mesg();

    std::cout << "Caught in means()\n";

    throw;             // rethrows the exception

}

After the code responds by displaying messages, it rethrows the exception, which means, in this case, sending the exception on up to main(). (In general, a rethrown exception rises to the next try-catch combination that catches that particular type of exception. If no handler is found, the program, by default, aborts.) Listing 15.12 uses the same header file (exc_mean.h in listing 15.10) as Listing 15.11.

Listing 15.12. error5.cpp

//error5.cpp -- unwinding the stack

#include

#include // or math.h, unix users may need -lm flag

#include

#include "exc_mean.h"

class demo

{

private:

    std::string word;

public:

    demo (const std::string & str)

    {

        word = str;

        std::cout << "demo " << word << " created\n";

    }

    ~demo()

    {

        std::cout << "demo " << word << " destroyed\n";

    }

    void show() const

    {

        std::cout << "demo " << word << " lives!\n";

    }

};

// function prototypes

double hmean(double a, double b);

double gmean(double a, double b);

double means(double a, double b);

int main()

{

    using std::cout;

    using std::cin;

    using std::endl;

    double x, y, z;

    {

        demo d1("found in block in main()");

        cout << "Enter two numbers: ";

        while (cin >> x >> y)

        {

               try {                  // start of try block

                   z = means(x,y);

                   cout << "The mean mean of " << x << " and " << y

                           << " is " << z << endl;

                   cout << "Enter next pair: ";

               } // end of try block

               catch (bad_hmean & bg)    // start of catch block

               {

                   bg.mesg();

                   cout << "Try again.\n";

                   continue;

               }

               catch (bad_gmean & hg)

               {

                   cout << hg.mesg();

                   cout << "Values used: " << hg.v1 << ", "

                           << hg.v2 << endl;

                   cout << "Sorry, you don't get to play any more.\n";

                   break;

               } // end of catch block

        }

        d1.show();

    }

    cout << "Bye!\n";

    cin.get();

    cin.get();

    return 0;

}

double hmean(double a, double b)

{

    if (a == -b)

        throw bad_hmean(a,b);

    return 2.0 * a * b / (a + b);

}

double gmean(double a, double b)

{

    if (a < 0 || b < 0)

        throw bad_gmean(a,b);

    return std::sqrt(a * b);

}

double means(double a, double b)

{

    double am, hm, gm;

    demo d2("found in means()");

Перейти на страницу:

Все книги серии Developer's Library

Похожие книги