Only use this in exceptional circumstances.
WARREN PEACE, The Lieutenant’s Tools
So far, we have shown you lots of code that explains how exception handling works in C++, but we have not yet shown even one example of a program that makes good and realistic use of exception handling. However, now that you know the mechanics of exception handling, this section can go on to explain exception-handling techniques.
We have given some very simple code in order to illustrate the basic concepts of exception handling. However, our examples were unrealistically simple. A more complicated but better guideline is to separate throwing an exception and catching the exception into separate functions. In most cases, you should include any throw
statement within a function definition, list the exception in the exception specification for that function, and place the catch
clause in a different function. Thus, the preferred use of the try-throw-catch
triad is as illustrated here:
void functionA()
throw (MyException)
{
.
.
.
throw MyException(<Maybe an argument>);
.
.
.
}
Then, in some other function (perhaps even some other function in some other file), you have
void functionB()
{
.
.
.
try
{
.
.
.
functionA();
.
.
.
}
catch(MyException e)
{
<Handle exception>
}
.
.
.
}
Moreover, even this kind of use of a throw
statement should be reserved for cases in which it is unavoidable. If you can easily handle a problem in some other way, do not throw an exception. Reserve throw
statements for situations in which the way the exceptional condition is handled depends on how and where the function is used. If the way that the exceptional condition is handled depends on how and where the function is invoked, then the best
thing to do is to let the programmer who invokes the function handle the exception. In all other situations, it is almost always preferable to avoid throwing exceptions.
It can be very useful to define a hierarchy of exception classes. For example, you might have an ArithmeticError
exception class and then define an exception class DivideByZeroError
that is a derived class of ArithmeticError
. Since a DivideByZeroError
is an ArithmeticError
, every catch
block for an ArithmeticError
will catch a DivideByZeroError
. If you list ArithmeticError
in an exception specification, then you have, in effect, also added DivideByZeroError
to the exception specification, whether or not you list DivideByZeroError
by name in the exception specification.
In Chapter 13, we created new dynamic variables with code such as the following:
struct Node
{
int data;
Node *link;
};
typedef Node* NodePtr;
. . .
NodePtr pointer = new Node;
This works fine as long as there is sufficient memory available to create the new node. But, what happens if there is not sufficient memory? If there is not sufficient memory to create the node, then a bad_alloc
exception is thrown. The type bad_alloc
is part of the C++ language. You do not need to define it.
Since new
will throw a bad_alloc
exception when there is not enough memory to create the node, you can check for running out of memory as follows:
try
{
NodePtr pointer = new Node;
}
catch (badAlloc)
{
cout << "Ran out of memory!";
}
Of course, you can do other things besides simply giving a warning message, but the details of what you do will depend on your particular programming task.
It is legal to throw an exception within a catch
block. In rare cases, you may want to catch an exception and then, depending on the details, decide to throw the same or a different exception for handling farther up the chain of exception-handling blocks.
What happens when an exception is never caught?
Can you nest a try
block inside another try
block?