5.6. try
Blocks and Exception Handling
AdvancedExceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program. Dealing with anomalous behavior can be one of the most difficult parts of designing any system.
Exception handling is generally used when one part of a program detects a problem that it cannot resolve and the problem is such that the detecting part of the program cannot continue. In such cases, the detecting part needs a way to signal that something happened and that it cannot continue. Moreover, the detecting part needs a way to signal the problem without knowing what part of the program will deal with the exceptional condition. Having signaled what happened, the detecting part stops processing.
A program that contains code that might raise an exception (usually) has another part to handle whatever happened. For example, if the problem is invalid input, the handling part might ask the user to provide correct input. If the database was lost, the handling part might alert an operator.
Exception handling supports this cooperation between the detecting and handling parts of a program. In C++, exception handling involves
throw
expressions, which the detecting part uses to indicate that it encountered something it can’t handle. We say that athrow
raises an exception.try
blocks, which the handling part uses to deal with an exception. Atry
block starts with the keywordtry
and ends with one or morecatch
clauses. Exceptions thrown from code executed inside atry
block are usually handled by one of thecatch
clauses. Because they “handle” the exception,catch
clauses are also known as exception handlers.- A set of
exception
classes that are used to pass information about what happened between athrow
and an associatedcatch
.
In the remainder of this section, we’ll introduce these three components of exception handling. We’ll also have more to say about exceptions in § 18.1 (p. 772).
5.6.1. A throw
Expression
The detecting part of a program uses a throw
expression to raise an exception. A throw
consists of the keyword throw
followed by an expression. The type of the expression determines what kind of exception is thrown. A throw
expression is usually followed by a semicolon, making it into an expression statement.
As a simple example, recall the program in § 1.5.2 (p. 23) that added two objects of type Sales_item
. That program checked whether the records it read referred to the same book. If not, it printed a message and exited.
Sales_item item1, item2;
cin >> item1 >> item2;
// first check that item1 and item2 represent the same book
if (item1.isbn() == item2.isbn()) {
cout << item1 + item2 << endl;
return 0; // indicate success
} else {
cerr << "Data must refer to same ISBN"
<< endl;
return -1; // indicate failure
}
In a more realistic program, the part that adds the objects might be separated from the part that manages the interaction with a user. In this case, we might rewrite the test to throw an exception rather than returning an error indicator:
// first check that the data are for the same item
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
// if we're still here, the ISBNs are the same
cout << item1 + item2 << endl;
In this code, if the ISBNs differ, we throw an expression that is an object of type runtime_error
. Throwing an exception terminates the current function and transfers control to a handler that will know how to handle this error.
The type runtime_error
is one of the standard library exception types and is defined in the stdexcept
header. We’ll have more to say about these types in § 5.6.3 (p. 197). We must initialize a runtime_error
by giving it a string
or a C-style character string (§ 3.5.4, p. 122). That string provides additional information about the problem.
5.6.2. The try
Block
The general form of a try
block is
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} // . . .
A try
block begins with the keyword try
followed by a block, which, as usual, is a sequence of statements enclosed in curly braces.
Following the try
block is a list of one or more catch
clauses. A catch
consists of three parts: the keyword catch
, the declaration of a (possibly unnamed) object within parentheses (referred to as an exception declaration), and a block. When a catch
is selected to handle an exception, the associated block is executed. Once the catch
finishes, execution continues with the statement immediately following the last catch
clause of the try
block.
The program-statements inside the try
constitute the normal logic of the program. Like any other blocks, they can contain any C++ statement, including declarations. As with any block, variables declared inside a try
block are inaccessible outside the block—in particular, they are not accessible to the catch
clauses.
Writing a Handler
In the preceding example, we used a throw
to avoid adding two Sales_item
s that represented different books. We imagined that the part of the program that added two Sales_item
s was separate from the part that communicated with the user. The part that interacts with the user might contain code something like the following to handle the exception that was thrown:
while (cin >> item1 >> item2) {
try {
// execute code that will add the two Sales_items
// if the addition fails, the code throws a runtime_error exception
} catch (runtime_error err) {
// remind the user that the ISBNs must match and prompt for another pair
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break; // break out of the while loop
}
}
The ordinary logic of the program that manages the interaction with the user appears inside the try
block. This part of the program is wrapped inside a try
because it might throw an exception of type runtime_error
.
This try
block has a single catch
clause, which handles exceptions of type runtime_error
. The statements in the block following the catch
are executed if code inside the try
block throws a runtime_error
. Our catch
handles the error by printing a message and asking the user to indicate whether to continue. If the user enters ’n
’, then the break
is executed and we exit the while
. Otherwise, execution falls through to the closing brace of the while
, which transfers control back to the while
condition for the next iteration.
The prompt to the user prints the return from err.what()
. We know that err
has type runtime_error
, so we can infer that what
is a member function (§ 1.5.2, p. 23) of the runtime_error
class. Each of the library exception classes defines a member function named what
. These functions take no arguments and return a C-style character string (i.e., a const char*
). The what
member of runtime_error
returns a copy of the string
used to initialize the particular object. If the code described in the previous section threw an exception, then this catch
would print
Data must refer to same ISBN
Try Again? Enter y or n
Functions Are Exited during the Search for a Handler
In complicated systems, the execution path of a program may pass through multiple try
blocks before encountering code that throws an exception. For example, a try
block might call a function that contains a try
, which calls another function with its own try
, and so on.
The search for a handler reverses the call chain. When an exception is thrown, the function that threw the exception is searched first. If no matching catch
is found, that function terminates. The function that called the one that threw is searched next. If no handler is found, that function also exits. That function’s caller is searched next, and so on back up the execution path until a catch
of an appropriate type is found.
If no appropriate catch
is found, execution is transferred to a library function named terminate
. The behavior of that function is system dependent but is guaranteed to stop further execution of the program.
Exceptions that occur in programs that do not define any try
blocks are handled in the same manner: After all, if there are no try
blocks, there can be no handlers. If a program has no try
blocks and an exception occurs, then terminate
is called and the program is exited.
INFO
Caution: Writing Exception Safe Code is Hard
It is important to realize that exceptions interrupt the normal flow of a program. At the point where the exception occurs, some of the computations that the caller requested may have been done, while others remain undone. In general, bypassing part of the program might mean that an object is left in an invalid or incomplete state, or that a resource is not freed, and so on. Programs that properly “clean up” during exception handling are said to be exception safe. Writing exception safe code is surprisingly hard, and (largely) beyond the scope of this language Primer.
Some programs use exceptions simply to terminate the program when an exceptional condition occurs. Such programs generally don’t worry about exception safety.
Programs that do handle exceptions and continue processing generally must be constantly aware of whether an exception might occur and what the program must do to ensure that objects are valid, that resources don’t leak, and that the program is restored to an appropriate state.
We will occasionally point out particularly common techniques used to promote exception safety. However, readers whose programs require robust exception handling should be aware that the techniques we cover are insufficient by themselves to achieve exception safety.
5.6.3. Standard Exceptions
The C++ library defines several classes that it uses to report problems encountered in the functions in the standard library. These exception classes are also intended to be used in the programs we write. These classes are defined in four headers:
- The
exception
header defines the most general kind of exception class namedexception
. It communicates only that an exception occurred but provides no additional information. - The
stdexcept
header defines several general-purpose exception classes, which are listed in Table 5.1.
Table 5.1. Standard Exception Classes Defined in <stdexcept>
Exception Class | Function |
---|---|
exception | The most general kind of problem. |
runtime_error | Problem that can be detected only at run time. |
range_error | Run-time error: result generated outside the range of values that are meaningful. |
overflow_error | Run-time error: computation that overflowed. |
underflow_error | Run-time error: computation that underflowed. |
logic_error | Error in the logic of the program. |
domain_error | Logic error: argument for which no result exists. |
invalid_argument | Logic error: inappropriate argument, |
length_error | Logic error: attempt to create an object larger than the maximum size for that type. |
out_of_range | Logic error: used a value outside the valid range. |
- The
new
header defines thebad_alloc
exception type, which we cover in § 12.1.2 (p. 458). - The
type_info
header defines thebad_cast
exception type, which we cover in § 19.2 (p. 825).
The library exception classes have only a few operations. We can create, copy, and assign objects of any of the exception types.
We can only default initialize (§ 2.2.1, p. 43) exception
, bad_alloc
, and bad_cast
objects; it is not possible to provide an initializer for objects of these exception types.
The other exception types have the opposite behavior: We can initialize those objects from either a string
or a C-style string, but we cannot default initialize them. When we create objects of any of these other exception types, we must supply an initializer. That initializer is used to provide additional information about the error that occurred.
The exception types define only a single operation named what
. That function takes no arguments and returns a const char*
that points to a C-style character string (§ 3.5.4, p. 122). The purpose of this C-style character string is to provide some sort of textual description of the exception thrown.
The contents of the C-style string that what
returns depends on the type of the exception object. For the types that take a string initializer, the what
function returns that string. For the other types, the value of the string that what
returns varies by compiler.
INFO
Exercises Section 5.6.3
Exercise 5.23: Write a program that reads two integers from the standard input and prints the result of dividing the first number by the second.
Exercise 5.24: Revise your program to throw an exception if the second number is zero. Test your program with a zero input to see what happens on your system if you don’t catch
an exception.
Exercise 5.25: Revise your program from the previous exercise to use a try
block to catch
the exception. The catch
clause should print a message to the user and ask them to supply a new number and repeat the code inside the try
.