Tải bản đầy đủ (.pdf) (20 trang)

C++ Primer Plus (P51) docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (629.14 KB, 20 trang )

return 0;
}
Here is a sample run:
Please enter your name. You will be served in the order of arrival.
name: Kinsey Millhone
Please enter your name. You will be served in the order of arrival.
name: Adam Dalgliesh
Please enter your name. You will be served in the order of arrival.
name: Andrew Dalziel
Please enter your name. You will be served in the order of arrival.
name: Kay Scarpetta
Please enter your name. You will be served in the order of arrival.
name: Richard Jury
The queue is full. Processing begins!
Now processing Kinsey Millhone
Now processing Adam Dalgliesh
Now processing Andrew Dalziel
Now processing Kay Scarpetta
Now processing Richard Jury
Exceptions
Programs sometimes encounter runtime problems that prevent the program from
continuing normally. For example, a program may try to open an unavailable file, or it may
request more memory than is available, or it may encounter values it cannot abide.
Usually, programmers try to anticipate such calamities. C++ exceptions provide a powerful
and flexible tool for dealing with these situations. Exceptions were added to C++ recently,
and not all compilers have implemented them yet.
Before examining exceptions, let's look at some of the more rudimentary options available
to the programmer. As a test case, take a function that calculates the harmonic mean of
two numbers. The harmonic mean of two numbers is defined as the inverse of the average
of the inverses. This can be reduced to the following expression:
2.0 * x * y / (x + y)


This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Note that if y is the negative of x, this formula results in division by zero, a rather
undesirable operation. One way to handle this is to have the function call the abort()
function if one argument is the negative of the other. The abort() function has its prototype
in the cstdlib (or stdlib.h) header file. A typical implementation, if called, sends a message
like "abnormal program termination" to the standard error stream (the same as the one
used by cerr) and terminates the program. It also returns an implementation-dependent
value indicating failure to the operating system or, if the program was initiated by another
program, to the parent process. Whether abort() flushes file buffers (memory areas used
to store material for transfers to and from files) depends upon the implementation. If you
prefer, you can use exit(), which does flush file buffers, but without displaying a message.
Listing 15.7 shows a short program using abort().
Listing 15.7 error1.cpp
//error1.cpp use the abort() function
#include <iostream>
using namespace std;
#include <cstdlib>
double hmean(double a, double b);
int main()
{
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
z = hmean(x,y);
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
cout << "Enter next set of numbers <q to quit>: ";
}
cout << "Bye!\n";

return 0;
}
double hmean(double a, double b)
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
{
if (a == -b)
{
cout << "untenable arguments to hmean()\n";
abort();
}
return 2.0 * a * b / (a + b);
}
Here's a sample run:
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
untenable arguments to hmean()
abnormal program termination
Note that calling the abort() function from hmean() terminates the program directly without
returning first to main().
The program could avoid aborting by checking the values of x and y before calling the
hmean() function. However, it's not safe to rely upon a programmer to know (or care)
enough to perform such a check.
A more flexible approach than aborting is to use a function's return value to indicate a
problem. For example, the get(void) member of the ostream class ordinarily returns the
ASCII code for the next input character, but it returns the special value EOF if it
encounters the end of a file. This approach doesn't work for hmean(). Any numeric value
could be a valid return value, so there's no special value available to indicate a problem. In
this kind of situation, you can use a pointer argument or reference argument to get a value
back to the calling program and use the function return value to indicate success or failure.

The istream family of overloaded >> operators uses a variant of this technique. By
informing the calling program of the success or failure, you give the program the option of
taking actions other than aborting. Listing 15.8 shows an example of this approach. It
redefines hmean() as a bool function whose return value indicates success or failure. It
adds a third argument for obtaining the answer.
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Listing 15.8 error2.cpp
//error2.cpp __ return an error code
#include <iostream>
using namespace std;
#include <cfloat> // (or float.h) for DBL_MAX
bool hmean(double a, double b, double * ans);
int main()
{
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
if (hmean(x,y,&z))
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
else
cout << "One value should not be the negative "
<< "of the other - try again.\n";
cout << "Enter next set of numbers <q to quit>: ";
}
cout << "Bye!\n";
return 0;
}
bool hmean(double a, double b, double * ans)

{
if (a == -b)
{
*ans = DBL_MAX;
return false;
}
else
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
{
*ans = 2.0 * a * b / (a + b);
return true;
}
}
Here's a sample run:
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
One value should not be the negative of the other - try again.
Enter next set of numbers <q to quit>: 1 19
Harmonic mean of 1 and 19 is 1.9
Enter next set of numbers <q to quit>: q
Bye!
Program Notes
Here, the program design allowed the user to continue, bypassing the effects of bad input.
Of course, the design does rely upon the user to check the function return value, something
that programmers don't always do. For example, to keep the sample programs short, most
of the listings in this book don't check to see if new returns the null pointer or if cout was
successful in handling output.
You could use either a pointer or a reference for the third arguments. Many programmers
prefer using pointers for arguments of the built-in types, for it makes it obvious which

argument is being used for the answer.
The Exception Mechanism
Now let's see how you can handle problems with the exception mechanism. A C++
exception is a response to an exceptional circumstance that arises while a program is
running, such as an attempt to divide by zero. Exceptions provide a way to transfer control
from one part of a program to another. Handling an exception has three components:
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Throwing an exception
Catching an exception with a handler
Using a try block
A program throws an exception when a problem shows up. For example, you can modify
hmean() in Listing 15.7 to throw an exception instead of calling the abort() function. A
throw statement, in essence, is a jump; that is, it tells a program to jump to statements at
another location. The throw keyword indicates the throwing of an exception. It's followed
by a value, such as a character string or an object, indicating the nature of the exception.
A program catches an exception with an exception handler at the place in a program
where you want to handle the problem. The catch keyword indicates the catching of an
exception. A handler begins with the keyword catch followed, in parentheses, by a type
declaration indicating the type of exception to which it responds. That, in turn, is followed
by a brace-enclosed block of code indicating the actions to take. The catch keyword, along
with the exception type, serves as a label identifying the point in a program to which
execution should jump when an exception is thrown. An exception handler also is called a
catch block.
A try block identifies a block of code for which particular exceptions will be activated. It's
followed by one or more catch blocks. The try block itself is indicated by the keyword try
followed by a brace-enclosed block of code indicating the code for which exceptions will be
noticed.
The easiest way to see how these three elements fit together is to look at a short example,
such as that provided in Listing 15.9.
Listing 15.9 error3.cpp

//error3.cpp
#include <iostream>
using namespace std;
double hmean(double a, double b);
int main()
{
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = hmean(x,y);
} // end of try block
catch (const char * s) // start of exception handler
{
cout << s << "\n";
cout << "Enter a new pair of numbers: ";
continue;
} // end of handler
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
cout << "Enter next set of numbers <q to quit>: ";
}
cout << "Bye!\n";
return 0;
}
double hmean(double a, double b)
{
if (a == -b)

throw "bad hmean() arguments: a = -b not allowed";
return 2.0 * a * b / (a + b);
}
Here's a sample run:
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
bad hmean() arguments: a = -b not allowed
Enter a new pair of numbers: 1 19
Harmonic mean of 1 and 19 is 1.9
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Enter next set of numbers <q to quit>: q
Bye!
Program Notes
The try block looks like this:
try { // start of try block
z = hmean(x,y);
} // end of try block
If any statement in this block leads to an exception being thrown, the catch blocks after this
block will handle the exception. If the program called hmean() somewhere else outside
this (and any other) try block, it wouldn't have the opportunity to handle an exception.
Throwing an exception looks like this:
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
In this case, the thrown exception is the string "bad hmean() arguments: a = -b not
allowed". Executing the throw is a bit like executing a return statement in that it terminates
function execution. However, instead of returning control to the calling program, a throw
causes a program to back up through the sequence of current function calls until it finds
the function containing the try block. In Listing 15.9, that function is the same as the calling
function. Soon you'll see an example involving backing up more than one function.

Meanwhile, in this case, the throw passes program control back to main(). There, the
program looks for an exception handler (following the try block) that matches the type of
exception thrown.
The handler, or catch block, looks like this:
catch (char * s) // start of exception handler
{
cout << s << "\n";
cout << "Enter a new pair of numbers: ";
continue;
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
} // end of handler
It looks a bit like a function definition, but it's not. The keyword catch identifies this as a
handler, and the char * s means that this handler matches a thrown exception that is a
string. This declaration of s acts much like a function argument definition in that a matching
thrown exception is assigned to s. Also, if an exception does match this handler, the
program executes the code within the braces.
If a program completes executing statements in a try block without any exceptions being
thrown, it skips the catch block or blocks after the try block and goes to the first statement
following the handlers. So when the sample run processed the values 3 and 6, program
execution went directly to the output statement reporting the result.
Let's trace through the events in the sample run after the values 10 and -10 are passed to
the hmean() function. The if test causes hmean() to throw an exception. This terminates
execution of hmean(). Searching back, the program determines that hmean() was called
from within a try block in main(). It then looks for a catch block with a type matching the
exception type. The single catch block present has a char * parameter, so it does match.
Detecting the match, the program assigns the string "bad hmean() arguments: a = -b
not allowed" to the variable s. Next, the program executes the code in the handler. First, it
prints s, which is the caught exception. Then it prints instructions to the user to enter new
data. Finally, it executes a continue statement, which causes the program to skip the rest
of the while loop and jump to its beginning again. The fact that the continue takes the

program to the beginning of the loop illustrates the fact that handler statements are part of
the loop and that the catch line acts like a label directing program flow (see Figure 15.2).
Figure 15.2. Program flow with exceptions.
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
You might be wondering what happens if a function throws an exception and there's no try
block or else no matching handler. By default, the program eventually calls the abort()
function, but you can modify that behavior. We'll return to this topic later.
Exception Versatility
C++ exceptions offer versatility, for the try block lets you select which code gets checked
for exceptions and the handlers let you specify what gets done. For example, in Listing
15.9, the try block was inside the loop, so program execution continued inside the loop
after the exception was handled. By placing the loop inside the try block, you can make an
exception transfer execution to outside the loop, thus terminating the loop. Listing 15.10
illustrates that. It also demonstrates two more points:
You can qualify a function definition with an exception specification to indicate
which kinds of exceptions it throws.
A catch block can handle more than one source of exceptions.
To qualify a function prototype to indicate the kinds of exceptions it throws, append an
exception specification, which consists of the keyword throw followed by a
comma-separated list of exception types enclosed in parentheses:
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
double hmean(double a, double b) throw(const char *);
This accomplishes two things. First, it tells the compiler what sort of exception or
exceptions a function throws. If the function then throws some other type of exception, the
program will react to the faux pas by calling (eventually) the abort() function. (We'll
examine this behavior and how it can be modified in more detail later.) Second, using an
exception specification alerts anyone who reads the prototype that this particular function
throws an exception, reminding the reader that he or she may want to provide a try block
and a handler. Functions that throw more than one kind of exception can provide a
comma-separated list of exception types; the syntax imitates that of an argument list for a

function prototype. For example, the following prototype indicates a function that can throw
either a char * exception or a double exception:
double multi_err(double z) throw(const char *, double);
The same information that appears in a prototype, as you can see in Listing 15.10, also
should appear in the function definition.
Using empty parentheses in the exception specification indicates that the function does not
throw exceptions:
double simple(double z) throw(); // doesn't throw an exception
Listing 15.10, as mentioned earlier, places the entire while loop inside the try block. It also
adds a second exception-throwing function, gmean(). This function returns the geometric
mean of two numbers, which is defined as the square root of their product. This function
isn't defined for negative arguments, which provides grounds for throwing an exception.
Like hmean(), gmean() throws a string-type exception, so the same catch block will catch
exceptions from either of these two functions.
Listing 15.10 error4.cpp
//error4.cpp
#include <iostream>
using namespace std;
#include <cmath> // or math.h, unix users may need -lm flag
double hmean(double a, double b) throw(const char *);
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
double gmean(double a, double b) throw(const char *);
int main()
{
double x, y, z;
cout << "Enter two numbers: ";
try { // start of try block
while (cin >> x >> y)
{
z = hmean(x,y);

cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
cout << "Geometric mean of " << x << " and " << y
<< " is " << gmean(x,y) << "\n";
cout << "Enter next set of numbers <q to quit>: ";
}
} // end of try block
catch (const char * s) // start of catch block
{
cout << s << "\n";
cout << "Sorry, you don't get to play any more. ";
} // end of catch block
cout << "Bye!\n";
return 0;
}
double hmean(double a, double b) throw(const char *)
{
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b) throw(const char *)
{
if (a < 0 || b < 0)
throw "bad gmean() arguments: negative values not allowed";
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
return sqrt(a * b);
}
Here's a sample run that gets terminated by bad input for the hmean() function:
Enter two numbers: 1 100

Harmonic mean of 1 and 100 is 1.9802
Geometric mean of 1 and 100 is 10
Enter next set of numbers <q to quit>: 10 -10
bad hmean() arguments: a = -b not allowed
Sorry, you don't get to play any more. Bye!
Because the exception handler is outside the loop, bad input terminates the loop. After the
program finishes the code in the handler, it proceeds to the next line in the program, which
prints Bye!.
For comparison, here's a sample run that gets terminated by bad input for the gmean()
function:
Enter two numbers: 1 100
Harmonic mean of 1 and 100 is 1.9802
Geometric mean of 1 and 100 is 10
Enter next set of numbers <q to quit>: 3 -15
Harmonic mean of 3 and -15 is 7.5
bad gmean() arguments: negative values not allowed
Sorry, you don't get to play any more. Bye!
The message reveals which exception was handled.
Multiple Try Blocks
You have many choices about setting up try blocks. For example, you could handle the two
function calls individually, placing each within its own try block. That allows you to program
a different response for the two possible exceptions, as the following code shows:
while (cin >> x >> y)
{
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
try { // try block #1
z = hmean(x,y);
} // end of try block #1
catch (const char * s) // start of catch block #1
{

cout << s << "\n";
out << "Enter a new pair of numbers: ";
ontinue;
} // end of catch block #1
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
try { // try block #2
z = gmean(x,y);
} // end of try block #2
catch (const char * s) // start of catch block #2
{
cout << s << "\n";
cout << "Data entry terminated!\n";
break;
} // end of catch block #2
cout << "Enter next set of numbers <q to quit>: ";
}
Another possibility is to nest try blocks, as the next example shows:
try { // outer try block
while (cin >> x >> y)
{
try { // inner try block
z = hmean(x,y);
} // end of inner try block
catch (const char * s) // inner catch block
{
cout << s << "\n";
cout << "Enter a new pair of numbers: ";
continue;
} // end of inner catch block

This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << "\n";
cout << "Geometric mean of " << x << " and " << y
<< " is " << gmean(x,y) << "\n";
cout << "Enter next set of numbers <q to quit>: ";
}
} // end of outer try block
catch (const char * s) // outer catch block
{
cout << s << "\n";
cout << "Sorry, you don't get to play any more. ";
} // end of outer catch block
Here an exception thrown by hmean() gets caught by the inner exception handler, which
allows the loop to continue. But an exception thrown by gmean() gets caught by the outer
exception handler, which terminates the loop.
Unwinding the Stack
Suppose a try block doesn't contain a direct call to a function throwing an exception but
that it calls a function that calls a function that throws an exception. Execution still jumps
from the function in which the exception is thrown to the function containing the try block
and handlers. Doing so involves unwinding the stack, which we'll discuss now.
First, let's look at how C++ normally handles function calls and returns. C++ typically
handles function calls by placing information on a stack (Chapter 9, "Memory Models and
Name spaces"). 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
this 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, and so on, 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.
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Now suppose a function terminates via an exception throw 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 is the process 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, while 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.11 provides an example of unwinding the stack. In it, main() calls details(), and
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
details() calls hmean(). When hmean() throws an exception, control returns all the way to
main(), where it is caught. In the process, the automatic variables representing the
arguments to hmean() and details() are freed.
Listing 15.11 error5.cpp
//error5.cpp
#include <iostream>
using namespace std;
double hmean(double a, double b) throw(const char *);
void details(double a, double b) throw(const char *);
int main()

{
double x, y;
cout << "Enter two numbers: ";
try {
while (cin >> x >> y)
details(x,y);
}
catch (const char * s)
{
cout << s << "\n";
cout << "Sorry, you can't play anymore. ";
}
cout << "Bye!\n";
return 0;
}
void details(double a, double b) throw(const char *){
cout << "Harmonic mean of " << a << " and " << b
<< " is " << hmean(a,b) << "\n";
cout << "Enter next set of numbers <q to quit>: ";
}
double hmean(double a, double b) throw(const char *)
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
{
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
return 2.0 * a * b / (a + b);
}
Here's a sample run; note that throwing the exception skips directly to main(), keeping
details() from displaying the text that otherwise would have been displayed had hmean()
terminated normally.

Enter two numbers: 3 15
Harmonic mean of 3 and 15 is 5
Enter next set of numbers <q to quit>: 20 -20
bad hmean() arguments: a = -b not allowed
Sorry, you don't get to play any more. Bye!
More Options
You can set up a handler to catch any kind of exception. Also, if a try block is nested, you
can have its handlers pass control on up to the handlers for the containing try block.
To catch any exception, use the ellipses for the exception type:
catch ( ) { // statements }
To pass control to a containing try block, use throw without a following exception:
catch (const char * s)
{
cout << "Exception caught in inner loop.\n";
throw; // send to containing try block
}
This sample, for example, prints a message, then passes control to the containing try
block, where the program once again will look for a handler matching the original thrown
exception.
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
Note that there is more than one way for one try block to contain another. One way is to
nest one within another, as discussed earlier. Another way is for one try block to contain a
function call that invokes a function containing a try block. In the first case, the preceding
sample code would pass control to the outer try block. In the second case, the sample
code would cause the program to unwind the stack to find the next try block.
Exceptions and Classes
Exceptions aren't just for handling error conditions in regular functions. They also can be
part of a class design. For example, a constructor could throw an exception if a call to the
new operator fails. Or the overloaded [] operator for an array class can throw an exception
if an index is out of range. Often it's useful if the exception can carry information with it,

such as the value of an invalid index. One could use, say, a type int exception in that case,
but it's more useful to throw an exception that's an object. The type of object will help
identify the source of the exception. The earlier example with hmean() and qmean() had
the problem that both went through the same type (char char *) of exception, making it
cumbersome to set up catch blocks that discriminate between the two. By using objects,
you can design a different type object for each exception you wish to catch. And the object
itself can carry the needful information.
Tip
If you have a function throw an exception, define an
exception class to be used as the type of exception thrown.
In fact, the usual practice in using exceptions is to throw objects as exceptions and to catch
them by reference:
class problem { };

void super() throw (problem &)
{

if (oh_no)
{
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.
problem oops(); // construct object
throw oops; // throw it

}

try {
super();
}
catch(problem & p)
{


}
Incidentally, while the throw-catch mechanism is much like function argument passing,
there are a few differences. For example, the compiler always creates a temporary copy
when throwing an exception, so in the preceding sample code, p would refer to a copy of
oops rather than oops. That's a good thing, because oops no longer exists after super()
terminates. Often it is simpler to combine construction with the throw:
throw problem(); // construct and throw type problem object
When exceptions refer to class processes, it's often useful if the exception type is defined
as a nested class. Not only does this make the exception type indicate the class originating
an exception, it helps prevent name conflicts. For example, suppose you have a class
called ArrayDbE in which you publicly declare another class called BadIndex. If the []
operator finds a bad index value, it can throw an exception of type BadIndex. Then a
handler for this exception would look like this:
catch (ArrayDbE::BadIndex &) { }
The ArrayDbE:: qualifier identifies BadIndex as being declared in the ArrayDbE class. It
also informs a reader that this handler is intended for exceptions generated by ArrayDbE
objects. The BadIndex name gives the reader a pretty good idea as to the nature of the
exception. This sounds rather attractive, so let's develop the idea. In particular, let's add
exceptions to the ArrayDb class first developed in Chapter 14, "Reusing Code in C++."
To the header file, add an exception BadIndex class, that is, a class defining objects to be
thrown as exceptions. As outlined earlier, it will be used for bad index values, and it will
This document was created by an unregistered ChmMagic, please go to to register it. Thanks.

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×