Team LiB
Previous Section Next Section

5.6. try Blocks and Exception Handling

 
Image

Exceptions 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 a throw raises an exception.

 

try blocks, which the handling part uses to deal with an exception. A try block starts with the keyword try and ends with one or more catch clauses. Exceptions thrown from code executed inside a try block are usually handled by one of the catch 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 a throw and an associated catch.

 

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_items that represented different books. We imagined that the part of the program that added two Sales_items 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.

 

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 named exception. 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>

 
Image
 

• The new header defines the bad_alloc exception type, which we cover in § 12.1.2 (p. 458).

 

• The type_info header defines the bad_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.

 

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.


 
Team LiB
Previous Section Next Section