Well, the program works for most cases. I didn’t know it had to work for that case.
COMPUTER SCIENCE STUDENT, APPEALING A GRADE
Exception handling is meant to be used sparingly and in situations that are more involved than what is reasonable to include in a simple introductory example. So, we will teach you the exception-handling details of C++ by means of simple examples that would not normally use exception handling. This makes a lot of sense for learning about exception handling, but do not forget that these first examples are toy examples, and in practice, you would not use exception handling for anything that simple.
For this example, suppose that milk is such an important food in our culture that people almost never run out of it, but still we would like our programs to accommodate the very unlikely situation of running out of milk. The basic code, which assumes we do not run out of milk, might be as follows:
cout << "Enter number of donuts:\n";
cin >> donuts;
cout << "Enter number of glasses of milk:\n";
cin >> milk;
dpg = donuts/static_cast<double>(milk);
cout << donuts << " donuts.\n"
<< milk << " glasses of milk.\n"
<< "You have " << dpg
<< " donuts for each glass of milk.\n";
If there is no milk, then this code will include a division by zero, which is an error. To take care of the special situation in which we run out of milk, we can add a test for this unusual situation. The complete program with this added test for the special situation is shown in Display 16.1. The program in Display 16.1 does not use exception handling. Now, let’s see how this program can be rewritten using the C++ exception-handling facilities.
In Display 16.2, we have rewritten the program from Display 16.1 using an exception. This is only a toy example, and you would probably not use an exception in this case. However, it does give us a simple example. Although the program as a whole is not simpler, at least the part between the words try
and catch
is cleaner, and this hints at the advantage of using exceptions. Look at the code between the words try
and catch
. That code is basically the same as the code in Display 16.1, but rather than the big if-else
statement (shown in color in Display 16.1) this new program has the following smaller if
statement (plus some simple nonbranching statements):
if (milk <= 0)
throw donuts;
This if
statement says that if there is no milk, then do something exceptional. That something exceptional is given after the word catch
. The idea is that the normal situation is handled by the code following the word try
, and that the code following the word catch
is used only in exceptional circumstances. We have thus separated the normal case from the exceptional case. In this toy example, this separation does not really buy us too much, but in other situations it will prove to be very helpful. Let’s look at the details.
The basic way of handling exceptions in C++ consists of the try-throw-catch
threesome. A try
block has the syntax
try
{
Some_Code
}
This try
block contains the code for the basic algorithm that tells the computer what to do when everything goes smoothly. It is called a try
block because you are not 100 percent sure that all will go smoothly, but you want to “give it a try.”
Now if something does go wrong, you want to throw an exception, which is a way of indicating that something went wrong. The basic outline, when we add a throw
, is as follows:
try
{
Code_To_Try
Possibly_Throw_An_Exception
More_Code
}
The following is an example of a try
block with a throw
statement included (copied from Display 16.2):
try
{
cout << "Enter number of donuts:\n";
cin >> donuts;
cout << "Enter number of glasses of milk:\n";
cin >> milk;
if (milk <= 0)
throw donuts;
dpg = donuts/static_cast<double>(milk);
cout << donuts << " donuts.\n"
<< milk << " glasses of milk.\n"
<< "You have " << dpg
<< " donuts for each glass of milk.\n";
}
The following statement throws the int
value donuts:
throw donuts;
The value thrown, in this case donuts
, is sometimes called an exception, and the execution of a throw
statement is called throwing an exception. You can throw a value of any type. In this case, an int
value is thrown.
As the name suggests, when something is “thrown,” something goes from one place to another place. In C++, what goes from one place to another is the flow of control (as well as the value thrown). When an exception is thrown, the code in the surrounding try
block stops executing and another portion of code, known as a catch
block, begins execution. This executing of the catch
block is called catching the exception or handling the exception. When an exception is thrown, it should ultimately be handled by (caught by) some catch
block. In Display 16.2, the appropriate catch
block immediately follows the try
block. We repeat the catch
block here:
catch(int e)
{
cout << e << " donuts, and No Milk!\n"
<< "Go buy some milk.\n";
}
This catch
block looks very much like a function definition that has a parameter of a type int
. It is not a function definition, but in some ways, a catch
block is like a function. It is a separate piece of code that is executed when your program encounters (and executes) the following (within the preceding try
block):
throw Some_int;
So, this throw
statement is similar to a function call, but instead of calling a function, it calls the catch
block and says to execute the code in the catch
block. A catch
block is often referred to as an exception handler, which is a term that suggests that a catch
block has a function-like nature.
What is that identifier e
in the following line from a catch
block?
catch(
int e)
That identifier e
looks like a parameter and acts very much like a parameter. So, we will call this e
the catch
-block parameter. (But remember, this does not mean that the catch
block is a function.) The catch
-block parameter does two things:
The catch
-block parameter is preceded by a type name that specifies what kind of thrown value the catch
block can catch.
The catch
-block parameter gives you a name for the thrown value that is caught, so you can write code in the catch
block that does things with the thrown value that is caught.
We will discuss these two functions of the catch
-block parameter in reverse order. In this subsection, we will discuss using the catch
-block parameter as a name for the value that was thrown and is caught. In the subsection entitled “Multiple Throws and Catches,” later in this chapter, we will discuss which catch
block (which exception handler) will process a value that is thrown. Our current example has only one catch
block. A common name for a catch
-block parameter is e
, but you can use any legal identifier in place of e
.
Let’s see how the catch
block in Display 16.2 works. When a value is thrown, execution of the code in the try
block ends and control passes to the catch
block (or blocks) that are placed right after the try
block. The catch
block from Display 16.2 is reproduced here:
catch(int e)
{
cout << e << " donuts, and No Milk!\n"
<< "Go buy some milk.\n";
}
When a value is thrown, the thrown value must be of type int
in order for this particular catch
block to apply. In Display 16.2, the value thrown is given by the variable donuts
, and since donuts
is of type int
, this catch
block can catch the value thrown.
Suppose the value of donuts
is 12
and the value of milk
is 0
, as in the second sample dialogue in Display 16.2. Since the value of milk
is not positive, the throw
statement within the if
statement is executed. In that case, the value of the variable donuts
is thrown. When the catch
block in Display 16.2 catches the value of donuts
, the value of donuts
is plugged in for the catch
-block parameter e
and the code in the catch
block is executed, producing the following output:
12 donuts, and No Milk!
Go buy some milk.
If the value of donuts
is positive, the throw
statement is not executed. In this case, the entire try
block is executed. After the last statement in the try
block is executed, the statement after the catch
block is executed. Note that if no exception is thrown, then the catch
block is ignored.
This makes it sound like a try-throw-catch
setup is equivalent to an if-else
statement. It almost is equivalent, except for the value thrown. A try-throw-catch
setup is similar to an if-else
statement with the added ability to send a message to one of the branches. This does not sound much different from an if-else
statement, but it turns out to be a big difference in practice.
To summarize in a more formal tone, a try
block contains some code that we are assuming includes a throw
statement. The throw
statement is normally executed only in exceptional circumstances, but when it is executed, it throws a value of some type. When an exception (a value like donuts
in Display 16.2) is thrown, that is the end of the try
block. All the rest of the code in the try
block is ignored and control passes to a suitable catch
block. A catch
block applies only to an immediately preceding try
block. If the exception is thrown, then that exception object is plugged in for the catch
-block parameter, and the statements in the catch
block are executed. For example, if you look at the dialogues in Display 16.2, you will see that as soon
as the user enters a nonpositive number, the try
block stops and the catch
block is executed. For now, we will assume that every try
block is followed by an appropriate catch
block. We will later discuss what happens when there is no appropriate catch
block.
Next, we summarize what happens when no exception is thrown in a try
block. If no exception (no value) is thrown in the try
block, then after the try
block is completed, program execution continues with the code after the catch
block. In other words, if no exception is thrown, then the catch
block is ignored. Most of the time when the program is executed, the throw
statement will not be executed, and so in most cases, the code in the try
block will run to completion and the code in the catch
block will be ignored completely.
What output is produced by the following code?
int waitTime = 46;
try
{
cout << "Try block entered.\n";
if (waitTime > 30)
throw waitTime;
cout << "Leaving try block.\n";
}
catch(int thrownValue)
{
cout << "Exception thrown with\n"
<< "waitTime equal to " << thrownValue << endl;
}
cout << "After catch block." << endl;
What would be the output produced by the code in Self-Test Exercise 1 if we make the following change? Change the line
int waitTime = 46;
to
int waitTime = 12;
In the code given in Self-Test Exercise 1, what is the throw
statement?
What happens when a throw
statement is executed? This is a general question. Tell what happens in general, not simply what happens in the code in Self-Test Question 1 or some other sample code.
In the code given in Self-Test Exercise 1, what is the try
block?
In the code given in Self-Test Exercise 1, what is the catch
block?
In the code given in Self-Test Exercise 1, what is the catch
-block parameter?
A throw
statement can throw a value of any type. A common thing to do is to define a class whose objects can carry the precise kind of information you want thrown to the catch
block. An even more important reason for defining a specialized exception class is so that you can have a different type to identify each possible kind of exceptional situation.
An exception class is just a class. What makes it an exception class is how it’s used. Still, it pays to take some care in choosing an exception class’s name and other details. Display 16.3 contains an example of a program with a programmer-defined exception class. This is just a toy program to illustrate some C++ details about exception handling. It uses much too much machinery for such a simple task, but it is an otherwise uncluttered example of some C++ details.
Notice the throw
statement, reproduced in what follows:
throw NoMilk(donuts);
The part NoMilk(donuts)
is an invocation of a constructor for the class NoMilk
. The constructor takes one int
argument (in this case donuts
) and creates an object of the class NoMilk
. That object is then “thrown.”
A try
block can potentially throw any number of exception values, and they can be of differing types. In any one execution of the try
block, only one exception will be thrown (since a thrown exception ends the execution of the try
block), but different types of exception values can be thrown on different occasions when the try
block is executed. Each catch
block can only catch values of one type, but you can catch exception values of differing types by placing more than one catch
block after a try
block. For example, the program in Display 16.4 has two catch
blocks after its try
block.
Note that there is no parameter in the catch
block for DivideByZero
. If you do not need a parameter, you can simply list the type with no parameter. This case is discussed a bit more in the Programming Tip section entitled “Exception Classes Can Be Trivial.”
Here we reproduce the definition of the exception class DivideByZero
from Display 16.4:
class DivideByZero
{};
This exception class has no member variables and no member functions (other than the default constructor). It has nothing but its name, but that is useful enough. Throwing an object of the class DivideByZero
can activate the appropriate catch
block, as it does in Display 16.4.
When using a trivial exception class, you normally do not have anything you can do with the exception (the thrown value) once it gets to the catch
block. The exception is just being used to get you to the catch
block. Thus, you can omit the catch
-block parameter. (You can omit the catch
-block parameter anytime you do not need it, whether the exception type is trivial or not.)
Sometimes it makes sense to delay handling an exception. For example, you might have a function with code that throws an exception if there is an attempt to divide by zero, but you may not want to catch the exception in that function. Perhaps some programs that use that function should simply end if the exception is thrown, and other programs that use the function should do something else. So you would not know what to do with the exception if you caught it inside the function. In these cases, it makes sense to not catch the exception in the function definition, but instead to have any program (or other code) that uses the function place the function invocation in a try
block and catch the exception in a catch
block that follows that try
block.
Look at the program in Display 16.5. It has a try
block, but there is no throw
statement visible in the try
block. The statement that does the throwing in that program is
if (bottom == 0)
throw DivideByZero();
This statement is not visible in the try
block. However, it is in the try
block in terms of program execution, because it is in the definition of the function safeDivide
and there is an invocation of safeDivide
in the try
block.
If a function does not catch an exception, it should at least warn programmers that any invocation of the function might possibly throw an exception. If there are exceptions that might be thrown, but not caught, in the function definition, then those exception types should be listed in an exception specification, which is illustrated by the following function declaration from Display 16.5:
double safeDivide(int top, int bottom) throw (DivideByZero);
As illustrated in Display 16.5, the exception specification should appear in both the function declaration and the function definition. If a function has more than one function declaration, then all the function declarations must have identical exception specifications. The exception specification for a function is also sometimes called the throw list.
If there is more than one possible exception that can be thrown in the function definition, then the exception types are separated by commas, as illustrated here:
void someFunction( ) throw (DivideByZero, OtherException);
All exception types listed in the exception specification are treated normally. When we say the exception is treated normally, we mean it is treated as we have described before this subsection. In particular, you can place the function invocation in a try
block followed by a catch
block to catch that type of exception, and if the function throws the exception (and does not catch it inside the function), then the catch
block following the try
block will catch the exception. If there is no exception specification (no throw list) at all (not even an empty one), then it is the same as if all possible exception types were listed in the exception specification; that is, any exception that is thrown is treated normally.
What happens when an exception is thrown in a function but is not listed in the exception specification (and not caught inside the function)? In that case, the program ends. In particular, notice that if an exception is thrown in a function but is not listed in the exception specification (and not caught inside the function), then it will not be caught by any catch
block, but instead your program will end. Remember, if there is no specification list at all, not even an empty one, then it is the same as if all exceptions were listed in the specification list, and so throwing an exception will not end the program in the way described in this paragraph.
Keep in mind that the exception specification is for exceptions that “get outside” the function. If they do not get outside the function, they do not belong in the exception specification. If they get outside the function, they belong in the exception specification no matter where they originate. If an exception is thrown in a try
block that is inside a function definition and is caught in a catch
block inside the function definition, then its type need not be listed in the exception specification. If a function definition includes an invocation of another function and that other function can throw an exception that is not caught, then the type of the exception should be placed in the exception specification.
To say that a function should not throw any exceptions that are not caught inside the function, you use an empty exception specification like so:
void someFunction( ) throw ( );
By way of summary:
void someFunction( ) throw (DivideByZero, OtherException);
//Exceptions of type DivideByZero or OtherException are
//treated normally. All other exceptions end the program
//if not caught in the function body.
void someFunction( ) throw ( );
//Empty exception list; all exceptions end the
//program if thrown but not caught in the function body.
void someFunction( );
//All exceptions of all types treated normally.
Keep in mind that an object of a derived class1 is also an object of its base class. So, if D is a derived class of class B and B is in the exception specification, then a thrown object of class D will be treated normally, since it is an object of class B and B is in the exception specification. However, no automatic type conversions are done. If double
is in the exception specification, that does not account for throwing an int
value. You would need to include both int
and double
in the exception specification.
One final warning: Not all compilers treat the exception specification as they are supposed to. Some compilers essentially treat the exception specification as a comment, and so with those compilers, the exception specification has no effect on your code. This is another reason to place all exceptions that might be thrown by your functions in the exception specification. This way all compilers will treat your exceptions the same way. Of course, you could get the same compiler consistency by not having any exception specification at all, but then your program would not be as well documented and you would not get the extra error checking provided by compilers that do use the exception specification. With a compiler that does process the exception specification, your program will terminate as soon as it throws an exception that you did not anticipate. (Note that this is a run-time behavior, but which run-time behavior you get depends on your compiler.)
What is the output produced by the following program?
#include <iostream>
using namespace std;
void sampleFunction(double test) throw (int);
int main()
{
try
{
cout << "Trying.\n";
sampleFunction(98.6);
cout << "Trying after call.\n";
}
catch(int)
{
cout << "Catching.\n";
}
cout << "End of program.\n";
return 0;
}
void sampleFunction(double test) throw (int)
{
cout << "Starting sampleFunction.\n";
if (test < 100)
throw 42;
}
What is the output produced by the program in Self-Test Exercise 8 if the following change were made to the program? Change
sampleFunction(98.6);
in the try block to
sampleFunction(212);