am = (a + b) / 2.0; // arithmetic mean
try
{
hm = hmean(a,b);
gm = gmean(a,b);
}
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
std::cout << "Caught in means()\n";
throw; // rethrows the exception
}
d2.show();
return (am + hm + gm) / 3.0;
}
Here’s a sample run of the program:
demo found in block in main() created
Enter two numbers: 6 12
demo found in means() created
demo found in means() lives!
demo found in means() destroyed
The mean mean of 6 and 12 is 8.49509
6 -6
demo found in means() created
hmean(6, -6): invalid arguments: a = -b
Caught in means()
demo found in means() destroyed
hmean(6, -6): invalid arguments: a = -b
Try again.
6 -8
demo found in means() created
demo found in means() destroyed
gmean() arguments should be >= 0
Values used: 6, -8
Sorry, you don't get to play any more.
demo found in block in main() lives!
demo found in block in main() destroyed
Bye!
Program Notes
Let’s trace through the course of the sample run shown in the preceding section. First, as the demo constructor announces, an object is created in the block in main(). Next, means() is called, and another demo object is created. The means() function passes the values 6 and 12 on to hmean() and gmean(), and those functions return values to means(), which calculates a result and returns it. Before returning the result, means() invokes d2.show(). After returning the result, means() terminates, and the destructor for d2 is called automatically:
demo found in means() lives!
demo found in means() destroyed
The next input cycle sends the values 6 and -6 to means(), and then means() creates a new demo object and relays the values to hmean(). The hmean() function then throws a bad_hmean exception, which is caught by the catch block in means(), as is shown by the following output:
hmean(6, -6): invalid arguments: a = -b
Caught in means()
The throw statement in this block then terminates means() and sends the exception up to main(). The fact that d2.show() isn’t called demonstrates that the execution of means() is terminated. But note that the destructor for d2 is called:
demo found in means() destroyed
This illustrates an extremely important aspect of exceptions: As the program unwinds the stack to reach where an exception is caught, it frees automatic storage class variables on the stack. If a variable happens to be a class object, then the destructor for that object is called.
Meanwhile, the rethrown exception reaches main(), where the appropriate catch block catches and handles it:
hmean(6, -6): invalid arguments: a = -b
Try again.
Now the third input cycle begins, with 6 and -8 sent on to means(). Once again, means() creates a new demo object. It passes 6 and -8 to hmean(), which processes them without a problem. Then means() passes 6 and -8 to gmean(), which throws a bad_gmean exception. Because means()) doesn’t catch bad_gmean exceptions, the exception is passed on up to main(), and no further code in means() is executed. However, once again, as the program unwinds the stack, it frees local automatic variables, so the destructor for d2 is called:
demo found in means() destroyed
Finally, the bad_gmean handler in main() catches the exception and terminates the loop:
gmean() arguments should be >= 0
Values used: 6, -8
Sorry, you don't get to play any more.
Then the program terminates normally, displaying a few messages and automatically calling the destructor for d1. If the catch block used, say, exit(EXIT_FAILURE) instead of break, the program would terminate immediately, so you wouldn’t see these messages:
demo found in main() lives!
Bye!
However, you would still see this message:
demo found in main() destroyed
Again, the exception mechanism would attend to freeing automatic variables on the stack.
More Exception Features
Although the throw-catch mechanism is similar to function arguments and the function return mechanism, there are a few differences. One, which you’ve already encountered, is that a return statement in a function fun() transfers execution to the function that called fun(), but a throw transfers execution all the way up to the first function having a try-catch combination that catches the exception. For example, in Listing 15.12, when hmean() throws an exception, control passes up to means(), but when gmean() throws an exception, control passes up to main().
Another difference is that the compiler always creates a temporary copy when throwing an exception, even if the exception specifier and catch blocks specify a reference. For instance, consider this code:
class problem {...};
...
void super() throw (problem)
{
...
if (oh_no)
{
problem oops; // construct object
throw oops; // throw it
...
}
...
try {
super();
}
catch(problem & p)
{
// statements
}
Here, p would refer to a copy of oops rather than to oops. That’s a good thing because oops no longer exists after super() terminates. By the way, it is simpler to combine construction with the throw:
throw problem(); // construct and throw default problem object