Section 6.13. try Blocks and Exception Handling


6.13. try Blocks and Exception Handling

Handling errors and other anomalous behavior in programs can be one of the most difficult parts of designing any system. Long-lived, interactive systems such as communication switches and routers can devote as much as 90 percent of their code to error detection and error handling. With the proliferation of Web-based applications that run indefinitely, attention to error handling is becoming more important to more and more programmers.

Exceptions are run-time anomalies, such as running out of memory or encountering unexpected input. Exceptions exist outside the normal functioning of the program and require immediate handling by the program.

In well-designed systems, exceptions represent a subset of the program's error handling. Exceptions are most useful when the code that detects a problem cannot handle it. In such cases, the part of the program that detects the problem needs a way to transfer control to the part of the program that can handle the problem. The error-detecting part also needs to be able to indicate what kind of problem occurred and may want to provide additional information.

Exceptions support this kind of communication between the error-detecting and error-handling parts of a program. In C++ exception handling involves:

  1. tHRow expressions, which the error-detecting part uses to indicate that it encountered an error that it cannot handle. We say that a throw raises an exceptional condition.

  2. try blocks, which the error-handling part uses to deal with an exception. A TRy block starts with 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 known as handlers.

  3. A set of exception classes defined by the library, which are used to pass the information about an error between a throw and an associated catch.

In the remainder of this section we'll introduce these three components of exception handling. We'll have more to say about exceptions in Section 17.1 (p. 688).

6.13.1. A tHRow Expression

An exception is thrown using a throw expression, which consists of the keyword tHRow followed by an expression. A throw expression is usually followed by a semicolon, making it into an expression statement. The type of the expression determines what kind of exception is thrown.

As a simple example, recall the program on page 24 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;      std::cin >> item1 >> item2;      // first check that item1 and item2 represent the same book      if (item1.same_isbn(item2)) {          std::cout << item1 + item2 << std::endl;          return 0; // indicate success      } else {          std::cerr << "Data must refer to same ISBN"                    << std::endl;          return -1; // indicate failure      } 

In a less simple program that used Sales_items, 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 instead:

      // first check that data is for the same item      if (!item1.same_isbn(item2))          throw runtime_error("Data must refer to same ISBN");      // ok, if we're still here the ISBNs are the same      std::cout << item1 + item2 << std::endl; 

In this code we check whether the ISBNs differ. If so, we discontinue execution and transfer control to a handler that will know how to handle this error.

A throw takes an expression. In this case, that expression is an object of type runtime_error. The runtime_error type is one of the standard library exception types and is defined in the stdexcept header. We'll have more to say about these types shortly. We create a runtime_error by giving it a string, which provides additional information about the kind of problem that occurred.

6.13.2. The try Block

The general form of a try block is

      try {          program-statements      } catch (exception-specifier) {          handler-statements      } catch (exception-specifier) {          handler-statements      } //... 

A TRy block begins with the keyword try followed by a block enclosed in braces. Following the TRy block is a list of one or more catch clauses. A catch clause consists of three parts: the keyword catch, the declaration of a single type or single object within parentheses (referred to as an exception specifier), and a block, which as usual must be enclosed in curly braces. If the catch clause is selected to handle an exception, the associated block is executed. Once the catch clause finishes, execution continues with the statement immediately following the last catch clause.

The program-statements inside the TRy constitute the normal logic of the program. They can contain any C++ statement, including declarations. Like any block, a TRy block introduces a local scope, and variables declared within a try block cannot be referred to outside the try, including within the catch clauses.

Writing a Handler

In the preceeding 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 to 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 ISBN 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          }      } 

Following the TRy keyword is a block. That block would invoke the part of the program that processes Sales_item objects. That part 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 define the actions that will be 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 an 'n', then we break out of the while. Otherwise the loop continues by reading two new Sales_items.

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 (Section 1.5.2, p. 24) of the runtime_error class. Each of the library exception classes defines a member function named what. This function takes no arguments and returns a C-style character string. In the case of runtime_error, the C-style string that what returns is a copy of the string that was used to initialize the runtime_error. If the code described in the previous section threw an exception, then the output printed by this catch would be

      Data must refer to same ISBN      Try Again? Enter y or n 

Functions Are Exited during the Search for a Handler

In complex systems the execution path of a program may pass through multiple TRy blocks before encountering code that actually throws an exception. For example, a try block might call a function that contains a try, that 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, the function terminates, and the function that called the one that threw is searched for a matching catch. If no handler is found, then that function also exits and the function that called it is searched; and so on back up the execution path until a catch of an appropriate type is found.

If no catch clause capable of handling the exception exists, program execution is transferred to a library function named terminate, which is defined in the exception header. The behavior of that function is system dependent, but it usually aborts the program.

Exceptions that occur in programs that define no TRy blocks are handled in the same manner: After all, if there are no try blocks, there can be no handlers for any exception that might be thrown. If an exception occurs, then terminate is called and the program (ordinarily) is aborted.

Exercises Section 6.13.2

Exercise 6.23:

The bitset operation to_ulong tHRows an overflow_error exception if the bitset is larger than the size of an unsigned long. Write a program that generates this exception.

Exercise 6.24:

Revise your program to catch this exception and print a message.


6.13.3. Standard Exceptions

The C++ library defines a set of classes that it uses to report problems encountered in the functions in the standard library. These standard exception classes are also intended to be used in the programs we write. Library exception classes are defined in four headers:

  1. The exception header defines the most general kind of exception class named exception. It communicates only that an exception occurs but provides no additional information.

  2. The stdexcept header defines several general purpose exception classes. These types are listed in Table 6.1 on the following page.

    Table 6.1. Standard Exception Classes Defined in <stdexcept>

    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

    Problem that could be detected before run time.

    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.


  3. The new header defines the bad_alloc exception type, which is the exception thrown by new (Section 5.11, p. 174) if it cannot allocate memory.

  4. The type_info header defines the bad_cast exception type, which we will discuss in Section 18.2 (p. 772).

Standard Library Exception Classes

The library exception classes have only a few operations. We can create, copy, and assign objects of any of the exception types. The exception, bad_alloc, and bad_cast types define only a default constructor (Section 2.3.4, p. 50); it is not possible to provide an initializer for objects of these types. The other exception types define only a single constructor that takes a string initializer. When we define any of these other exception types, we must supply a string argument. That string 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*. The pointer it returns points to a C-style character string (Section 4.3, p. 130). 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 character array to which what returns a pointer depends on the type of the exception object. For the types that take a string initializer, the what function returns that string as a C-style character array. For the other types, the value returned varies by compiler.



C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net