Learn how to throw an exception, how to associate handlers, or catch clauses, with a set of program statements using a try block, how exceptions are handled by catch clauses, exception specifications, and design considerations for programs that use exceptions.

This chapter is from the book

This chapter is from the book

Exception handling is a mechanism that allows
two separately developed program components to
communicate when a program anomaly, called an
exception, is
encountered during the execution of the program.
In this chapter we first look at how to raise,
or throw, an exception at the location where the
program anomaly is encountered. We then look at
how to associate handlers, or catch clauses,
with a set of program statements using a try
block, and we look at how exceptions are handled
by catch clauses. We then introduce exception
specifications, a mechanism that associates a
list of exceptions with a function declaration
and that guarantees that the function does not
throw any other types of exceptions. We end the
chapter with a discussion of design
considerations for programs that use
exceptions.

11.1 Throwing an Exception

Exceptions are
run-time anomalies that a program may detect,
such as division by 0, access to an array
outside of its bounds, or the exhaustion of the
free store memory. Such exceptions exist outside
the normal functioning of the program and
require immediate handling by the program. The
C++ language provides built-in language features
to raise and handle exceptions. These language
features activate a run-time mechanism used to
communicate exceptions between two unrelated
(often separately developed) portions of a C++
program.

When an exception is encountered in a C++ program,
the portion of the program that detects the exception
can communicate that the exception has occurred by
raising, or throwing,
an exception. To see how exceptions are thrown in C++,
let's reimplement the class iStack presented in Section 4.15
to use exceptions to indicate anomalies in the handling
of the stack. The definition of the class iStack looks
like this:

The stack is implemented using a vector of
ints. When an iStack object is created, the
constructor for iStack creates a vector of ints
of the size specified with the initial value. This size
is the maximum number of elements the iStack object can
contain. The following, for example, creates an iStack
object called myStack that can contain as many
as 20 values of type int:

iStack myStack(20);

What can go wrong when we manipulate
myStack? Here are two anomalies that may be
encountered with our iStack class:

A pop() operation is
requested and the stack is empty.

A push() operation is
requested and the stack is full.

We decide that these anomalies should be communicated
to the functions manipulating iStack objects using
exceptions. So where do we start?

First, we must define the exceptions that can be
thrown. In C++, exceptions are most often implemented
using classes. Although classes are fully introduced in
Chapter 13, we will
define here two simple classes to use as exceptions with
our iStack class, and we place these class definitions
in the header stackExcp.h:

We must then change the definition of the member
functions pop() and push() to throw
these newly defined exceptions. An exception is thrown
using a throw
expression. A throw expression looks a great deal
like a return statement. A throw expression is composed
of the keyword throw followed by an expression
whose type is that of the exception thrown. What does
the throw expression in pop() look like? Let's
try this:

// oops, not quite right
throw popOnEmpty;

Unfortunately, this is not quite right. An exception
is an object, and pop() must throw an object of
class type. The expression in the throw expression
cannot simply be a type. To create an object of class
type, we need to call the class constructor. What does a
throw expression that invokes a constructor look like?
Here is the throw expression in pop():

// expression is a constructor call
throw popOnEmpty();

This throw expression creates an exception object of
type popOnEmpty.

Recall that the member functions pop() and
push() were defined to return a value of type
bool: a true return value indicates
that the operation succeeded, and a false
return value indicates that it failed. Because
exceptions are now used to indicate the failure of the
pop() and push() operations, the
return values from these functions are now unnecessary.
We now define these member functions with a
void return type. For example:

The functions that use our iStack class will now
assume that everything is fine unless an exception is
thrown; they no longer need to test the return value of
the member function pop() or push() to
see whether the operation succeeds. We will see how to
define a function to handle exceptions in the next two
sections.

We are now ready to provide the new implementations
of iStack's pop() and push() member
functions:

Although exceptions are most often objects of class
type, a throw expression can throw an object of any
type. For example, although it's unusual, the function
mathFunc() in the following code sample throws
an exception object of enumeration type. This code is
valid C++ code:

Exercise 11.2

The IntArray class defined in Section
2.3 has a member operator function
operator[]() that uses assert() to
indicate that the index is outside the bounds of
the array. Change the definition of
operator[]() to instead throw an exception
in this situation. Define an exception class to
be used as the type of the exception thrown.