17.2 Exception-Handling Flow of Control; Defining an Exception Class

Let’s consider a simple example of exception handling (Figs. 17.117.2). For demonstration purposes, we show how to deal with a common arithmetic problem—division by zero, which with integer arithmetic typically causes a program to terminate prematurely. In floating-point arithmetic, many C++ implementations allow division by zero, in which case a result of positive or negative infinity is typically displayed as inf or -inf, respectively. Typically, a program would simply test for division by zero before attempting the calculation—we use exceptions here to present the flow of control when a program executes successfully and when an exception occurs.

In this example, we define a function quotient that receives two integers entered by the user and divides the first by the second. Before performing the division, the function casts the first int parameter’s value to type double. Then, the second int parameter’s value is (implicitly) promoted to type double for the calculation. So function quotient actually performs the division using two double values and returns a double result.

For the purpose of this example we treat any attempt to divide by zero as an error. Thus, function quotient tests its second parameter to ensure that it isn’t zero before allowing the division to proceed. If the second parameter is zero, the function throws an exception to indicate to the caller that a problem occurred. The caller (main in this example) can then handle the exception and allow the user to type two new values before calling function quotient again. In this way, the program can continue executing even after an improper value is entered, thus making the program more robust.

The example consists of two files. DivideByZeroException.h (Fig. 17.1) defines an exception class that represents the type of the problem that might occur in the example, and fig17_02.cpp (Fig. 17.2) defines the quotient function and the main function that calls it—we’ll use quotient and main to explain the exception-handling flow of control.

17.2.1 Defining an Exception Class to Represent the Type of Problem That Might Occur

Figure 17.1 defines class DivideByZeroException as a derived class of Standard Library class runtime_error (from header <stdexcept>). Class runtime_error—a derived class of exception (from header <exception>)—is the C++ standard base class for representing runtime errors. Class exception is the standard C++ base class for exceptions in the C++ Standard Library. (Section 17.10 discusses class exception and its derived classes in detail.) A typical exception class that derives from the runtime_error class defines only a constructor (e.g., lines 10–11) that passes an error-message string to the base-class runtime_error constructor. Every exception class that derives directly or indirectly from exception contains the virtual function what, which returns an exception object’s error message. You’re not required to derive a custom exception class, such as DivideByZeroException, from the standard exception classes provided by C++. However, doing so allows you to use the virtual function what to obtain an appropriate error message and also allows you to polymorphically process the exceptions by catching a reference to the base-class type. We use an object of this DivideByZeroException class in Fig. 17.2 to indicate

when an attempt is made to divide by zero.

Fig. 17.1 Class DivideByZeroException definition.

Alternate View

 1   // Fig. 17.1: DivideByZeroException.h
 2   // Class DivideByZeroException definition.
 3   #include <stdexcept> // stdexcept header contains runtime_error
 4
 5   // DivideByZeroException objects should be thrown by functions
 6   // upon detecting division-by-zero exceptions
 7   class DivideByZeroException :  public std::runtime_error {
 8   public:
 9   // constructor specifies default error message
10    DivideByZeroException()
11       : std::runtime_error{"attempted to divide by zero"} {}
12   };

17.2.2 Demonstrating Exception Handling

Figure 17.2 uses exception handling to wrap code that might throw a DivideByZeroException and to handle that exception, should one occur. The user enters two integers, which are passed as arguments to function quotient (lines 10–18). This function divides its first parameter (numerator) by its second parameter (denominator). Assuming that the user does not specify 0 as the denominator for the division, function quotient returns the division result. If the user inputs 0 for the denominator, quotient throws an exception. In the sample output, the first two lines show a successful calculation, and the next two show a failure due to an attempt to divide by zero. When the exception occurs, the program informs the user of the mistake and prompts the user to input two new integers. After we discuss the code, we’ll consider the user inputs and flow of program control that yield these outputs.

Fig. 17.2 Example that throws exceptions on attempts to divide by zero.

Alternate View

 1   // Fig. 17.2: fig17_02.cpp
 2   // Example that throws exceptions on
 3   // attempts to divide by zero.
 4   #include <iostream>
 5   #include "DivideByZeroException.h" // DivideByZeroException class
 6   using namespace std;
 7
 8   // perform division and throw DivideByZeroException object if
 9   // divide-by-zero exception occurs
10   double quotient(int numerator, int denominator) {
11      // throw DivideByZeroException if trying to divide by zero
12      if (denominator == 0) {
13          throw DivideByZeroException{};// terminate function
14      }
15   
16     // return division result
17     return static_cast<double>(numerator) / denominator;
18   }
19
20   int main() {
21      int number1; // user-specified numerator
22      int number2; // user-specified denominator
23
24      cout << "Enter two integers (end-of-file to end): ";
25
26     // enable user to enter two integers to divide
27     while (cin >> number1 >> number2) {
28        // try block contains code that might throw exception
29        // and code that will not execute if an exception occurs
30       try {                                                        
31          double result{quotient(number1, number2)};                
32          cout << "The quotient is: " << result << endl;            
33       }                                                            
34       catch (const DivideByZeroException& divideByZeroException) {
35          cout << "Exception occurred: "                            
36             << divideByZeroException.what() << endl;               
37       }                                                            
38
39        cout << "
Enter two integers (end-of-file to end): ";
40     }
41
42      cout << endl;
43   }

Enter two integers (end-of-file to end): 100 7
The quotient is: 14.2857

Enter two integers (end-of-file to end): 100 0
Exception occurred: attempted to divide by zero

Enter two integers (end-of-file to end): ^Z

17.2.3 Enclosing Code in a try Block

The program begins by prompting the user to enter two integers. The integers are input in the condition of the while loop (line 27). Line 31 passes the values to function quotient (lines 10–18), which either divides the integers and returns a result, or throws an exception (i.e., indicates that an error occurred) on an attempt to divide by zero. Exception handling is geared to situations in which the function that detects an error is unable to handle it.

As you learned in Section 7.10, try blocks enable exception handling, enclosing statements that might cause exceptions and statements that should be skipped if an exception occurs. The try block in lines 30–33 encloses the invocation of function quotient and the statement that displays the division result. In this example, because the invocation of function quotient (line 31) can throw an exception, we enclose this function invocation in a try block. Enclosing the output statement (line 32) in the try block ensures that the output will occur only if function quotient returns a result.

Software Engineering Observation 17.3

Exceptions may surface through explicitly mentioned code in a try block, through calls to other functions (including library calls), through deeply nested function calls initiated by code in a try block and through operators like new (Section 17.8).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset