Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
You re not required to inform the people using your function
what exceptions you might throw. However, failure to do so can be considered
uncivilized because it means that users cannot be sure what code to write to
catch all potential exceptions. If they have your source code, they can hunt
through and look for throw statements, but often a library doesn t come
with sources. Good documentation can help alleviate this problem, but how many
software projects are well documented? C++ provides syntax to tell the user the
exceptions that are thrown by this function, so the user can handle them. This
is the optional exception specification, which adorns a function s
declaration, appearing after the argument list.
The exception specification reuses the keyword throw,
followed by a parenthesized list of all the types of potential exceptions that
the function can throw. Your function declaration might look like this:
void f() throw(toobig, toosmall, divzero);
As far as exceptions are concerned, the traditional function
declaration
means that any type of exception can be thrown from
the function. If you say
no exceptions whatsoever will be thrown from the
function (so you d better be sure that no functions farther down in the call
chain let any exceptions propagate up!).
For good coding policy, good documentation, and ease-of-use
for the function caller, consider using exception specifications when you write
functions that throw exceptions. (Variations on this guideline are discussed
later in this chapter.)
The unexpected( ) function
If your exception specification claims you re going to throw a certain set of exceptions and then you throw something that isn t in that set,
what s the penalty? The special function unexpected( ) is called
when you throw something other than what appears in the exception
specification. Should this unfortunate situation occur, the default unexpected( )
calls the terminate( ) function described earlier in this
chapter.
The set_unexpected( ) function
Like terminate( ), the unexpected( ) mechanism installs your own function to respond to unexpected exceptions. You do so
with a function called set_unexpected( ), which, like set_terminate( ),
takes the address of a function with no arguments and void return value.
Also, because it returns the previous value of the unexpected( )
pointer, you can save it and restore it later. To use set_unexpected( ),
include the header file <exception>. Here s an example that shows a
simple use of the features discussed so far in this section:
//: C01:Unexpected.cpp
// Exception specifications & unexpected(),
//{-msc} (Doesn t terminate properly)
#include <exception>
#include <iostream>
using namespace std;
class Up {};
class Fit {};
void g();
void f(int i) throw(Up, Fit) {
switch(i) {
case 1: throw Up();
case 2: throw Fit();
}
g();
}
// void g() {} // Version 1
void g() { throw 47; } // Version 2
void my_unexpected() {
cout << "unexpected exception thrown"
<< endl;
exit(0);
}
int main() {
set_unexpected(my_unexpected); // (Ignores return
value)
for(int i = 1; i <=3; i++)
try {
f(i);
} catch(Up) {
cout << "Up caught" <<
endl;
} catch(Fit) {
cout << "Fit caught" <<
endl;
}
} ///:~
The classes Up and Fit are created solely to
throw as exceptions. Often exception classes will be small, but they can
certainly hold additional information so that the handlers can query for it.
The f( ) function promises in its exception specification
to throw only exceptions of type Up and Fit, and from looking at
the function definition, this seems plausible. Version one of g( ),
called by f( ), doesn t throw any exceptions, so this is true. But
if someone changes g( ) so that it throws a different type of
exception (like the second version in this example, which throws an int),
the exception specification for f( ) is violated.
The my_unexpected( ) function has no arguments
or return value, following the proper form for a custom unexpected( )
function. It simply displays a message so that you can see that it was called,
and then exits the program (exit(0) is used here so that the book s make
process is not aborted). Your new unexpected( ) function should not
have a return statement.
In main( ), the try block is within a for
loop, so all the possibilities are exercised. In this way, you can achieve
something like resumption. Nest the try block inside a for, while,
do, or if and cause any exceptions to attempt to repair the
problem; then attempt the try block again.
Only the Up and Fit exceptions are caught
because those are the only exceptions that the programmer of f( )
said would be thrown. Version two of g( ) causes my_unexpected( )
to be called because f( ) then throws an int.
In the call to set_unexpected( ), the return
value is ignored, but it can also be saved in a pointer to function and be
restored later, as we did in the set_terminate( ) example earlier
in this chapter.
A typical unexpected handler logs the error and
terminates the program by calling exit( ). It can, however, throw
another exception (or rethrow the same exception) or call abort( ).
If it throws an exception of a type allowed by the function whose specification
was originally violated, the search resumes at the call of the function
with this exception specification. (This behavior is unique to unexpected( ).)
If the exception thrown from your unexpected handler
is not allowed by the original function s specification, one of the following
occurs:
1. If
std::bad_exception (defined in <exception>) was in the function s exception specification, the exception thrown from the unexpected
handler is replaced with a std::bad_exception object, and the search resumes
from the function as before.
2. If
the original function s specification did not include std::bad_exception,
terminate( ) is called.
The following program illustrates this behavior:
//: C01:BadException.cpp {-bor}
#include <exception> // For std::bad_exception
#include <iostream>
#include <cstdio>
using namespace std;
// Exception classes:
class A {};
class B {};
// terminate() handler
void my_thandler() {
cout << "terminate called" << endl;
exit(0);
}
// unexpected() handlers
void my_uhandler1() { throw A(); }
void my_uhandler2() { throw; }
// If we embed this throw statement in f or g,
// the compiler detects the violation and reports
// an error, so we put it in its own function.
void t() { throw B(); }
void f() throw(A) { t(); }
void g() throw(A, bad_exception) { t(); }
int main() {
set_terminate(my_thandler);
set_unexpected(my_uhandler1);
try {
f();
} catch(A&) {
cout << "caught an A from f"
<< endl;
}
set_unexpected(my_uhandler2);
try {
g();
} catch(bad_exception&) {
cout << "caught a bad_exception from
g" << endl;
}
try {
f();
} catch(...) {
cout << "This will never print"
<< endl;
}
} ///:~
The my_uhandler1( ) handler throws an acceptable
exception (A), so execution resumes at the first catch, which succeeds.
The my_uhandler2( ) handler does not throw a valid exception (B),
but since g specifies bad_exception, the B exception is
replaced by a bad_exception object, and the second catch also succeeds.
Since f does not include bad_exception in its specification, my_thandler( )
is called as a terminate handler. Here s the output:
caught an A from f
caught a bad_exception from g
terminate called
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |