Chapter 22. Exceptions

How glorious it is—and also how painful—to be an exception.

Alfred de Musset

Airplanes fly from one place to another and 99.9% of the time there’s no trouble. But when there is trouble, such as a stuck wheel or an engine fire, pilots are trained to handle the emergency.

Let’s examine in detail what happens during an airborne emergency such as an engine catching fire. This is an exception to normal flight. First, a fire alarm goes off in the cockpit. The alarm catches the pilots’ attention, and they start going through the fire-emergency procedure. This is an extensive list of things to do in case of fire. The airline prepared this list ahead of time, and the pilots have the list memorized. The pilots do what’s necessary to handle the exception: activate the fire extinguisher, shut down the engine, land very quickly, etc.

Let’s translate this procedure into C++ pseudocode. When the pilots take off they are going to try to fly the plane from one point to another without problems. The C++ “code” for this is:

try {
     fly_from_point_a_to_point_b(  );
}

The try keyword indicates that we are going to attempt an operation that may cause an exception.

But what happens when we get an exception? We need to handle it. The C++ code for this is:

catch (fire_emergency& fire_info) {
    active_extinguisher(fire_info.engine);
    turn_off(fire_info.engine);
    land_at_next_airport(  );
}

The keyword catch indicates that this section of code handles an exception. In this case the exception handled is a fire_emergency. This is the type of emergency. It could be a fire in engine number 1, engine number 2, or engine number 3 (assuming a three-engine plane). Which engine is on fire is stored in the variable fire_info.

The fire_emergency class describes what type of fire occurred. Its definition is:

class fire_emergency {
    public:
        int engine;    // Which engine is on fire
        // Other information about the fire
};

We’ve covered everything but the actual detection of the fire. Buried within each engine is a fire sensor. The code for this sensor is:

// Watch for fire in engine #2
void sensor_2(  ) {
    while (engine_running(  )) {
        if (engine_on_fire(  )) {
            fire_emergency fire_info;
            
            fire_info.engine = 2;
            throw(fire_info);
        }
    }
}

When this code senses a fire, it puts the information in a fire_emergency variable named fire_info and triggers an exception with the throw statement.

When the throw statement is executed, normal processing is stopped. After all, when a fire occurs, normal flying is stopped. Execution is transferred to the catch statement for the fire_emergency.

To summarize, exception handling consists of:

  • A description of a possible problem, in this case the fire_emergency class.

  • A section of code in which the exception may occur, which is enclosed in a try statement. In this case, the statement is fly_from_point_a_to_point_b( ).

  • Something that causes an exception and triggers the emergency procedures through a throw statement.

  • Exception-handling code inside a catch block.

Adding Exceptions to the Stack Class

In Chapter 13, we defined a simple stack. The push and pop functions perform bounds checking on the current stack location using assert statements. For example:

inline void stack::push(const int item)
{
    assert((count >= 0) &&
           (count < sizeof(data)/sizeof(data[0])));
    data[count] = item;
    ++count;
}

The assert statement aborts the program when count is out of range. This is a rather drastic way of handling the problem. A nicer way of doing things is to throw an exception. This gives the program a chance to catch the error instead of aborting. Think of how the pilots would feel if the plane displayed an error message and shut down every time there was a fire.

Now we’ll leave the plane world and take a look at real-world exceptions and see how to add exception handling to our stack class.

Creating an Exception

The first thing we need to do is decide what type of exceptions we are going to handle and describe them as classes. In our stack example, the only exception we expect is an out-of-bounds error. We’ll describe this error with a simple string. The class for an out-of-bounds error is:

class bound_err {
    public:
        const string what;     // What caused the error

        // Initialize the bound error with a message
        bound_err(const std::string& i_what): what(i_what) {}
        // bound_err&  operator = -- defaults 
        // bound_err(bound_err) -- default copy constructor
        // ~bound_err -- default destructor
};

Using a Try Block for Normal Execution

Exception checking starts with the keyword try . This tells C++ that exceptions may be generated in the section of code that follows and that they will be handled immediately after the try block. For example, if we are trying to perform a big stack operation, the code might look like this:

try {
    do_big_stack_operation(  );
};

Immediately after the try block, we need to use a catch statement to tell C++ what problems we will handle. The syntax for this statement is:

catch (problem_type& parameter) { 
    statements;
}

The problem_type is the class that describes what happened. For the out-of-bounds error, the catch statement looks like:

catch (bound_err& what_happened) { 
    std::cerr << "Error: Bounds exceeded
";
    std::cerr << "Reason: " << what_happened.what << '
';
}

Several catch statements may be used to catch different types of exceptions. If an exception is not caught, it is considered an unexpected exception and will cause a call to the unexpected-exception handler, which aborts the program by default. If you want to catch all exceptions, use “...” for the exception type. For example:

catch (bound_err& what_happened) { 
    // .... Body of catch
}
catch (...) {
    std::cerr << "Something strange happened
";
}

Throwing an Exception

Now we need to update our old stack program and replace all the “error-message-and-abort” code with throw statements. The new procedure for push now looks like this:

inline void stack::push(const int item)
{
    if ((count < 0) ||
        (count >= sizeof(data)/sizeof(data[0]))) {
        bound_err overflow("Push overflows stack");
        throw overflow;
    }
    data[count] = item;
    ++count;
}

Actually we don’t need a special variable for overflow. The code can be consolidated. In the previous example, I used two statements to show explicitly what is going on. The following code performs the same operation:

inline void stack::push(const int item)
{
    if ((count < 0) ||
        (count >= sizeof(data)/sizeof(data[0]))) {
        throw bound_err("Push overflows stack");
    }
    data[count] = item;
    ++count;
}

The basic function definition we’ve been using so far tells C++, “Expect any exception to be thrown at any time.” The push function can only throw a bound_err exception. C++ allows you to list all the possible exceptions in a function by putting a throw directive at the end of the function declaration:

inline void stack::push(const int item) throw(bound_err) {

But what happens if we throw an exception that’s not in the list of exceptions? C++ turns this into a call to the function unexpected( ). This normally causes the program to terminate.

Example 22-1 contains a stack that uses exceptions when something goes wrong.

Example 22-1. stack_c/stack_e1.cpp
/********************************************************
 * Stack                                                *
 *      A file implementing a simple stack class        *
 ********************************************************/
#include <cstdlib>
#include <iostream>
#include <cassert>

const int STACK_SIZE = 100;     // Maximum size of a stack

/********************************************************
 * bound_err -- a class used to handle out of bounds    *
 *              execeptions.                            *
 ********************************************************/
class bound_err {
    public:
        const string what;      // What caused the error

        // Initialize the bound error with a message
        bound_err(const string& i_what) what(i_what) {}
        // Assignment operator defaults
        // bound_err(bound_err) -- default copy constructor
        // ~ bound_err -- default destructor
};

/********************************************************
 * Stack class                                          *
 *                                                      *
 * Member functions                                     *
 *      init -- initialize the stack.                   *
 *      push -- put an item on the stack.               *
 *      pop -- remove an item from the stack.           *
 ********************************************************/
// The stack itself
class stack {
    private:
        int count;              // Number of items in the stack
        int data[STACK_SIZE];   // The items themselves
    public:
        // Initialize the stack
        stack(  ): count(0) {};
        // Copy constructor defaults
        // Assignment operator defaults

        // Push an item on the stack
        void push(const int item) throw(bound_err);

        // Pop an item from the stack
        int pop(  ) throw(bound_err);
};
/********************************************************
 * stack::push -- push an item on the stack.            *
 *                                                      *
 * Warning: We do not check for overflow.               *
 *                                                      *
 * Parameters                                           *
 *      item -- item to put in the stack                *
 ********************************************************/
inline void stack::push(const int item) throw(bound_err)
{
    if ((count < 0) ||
           (count >= sizeof(data)/sizeof(data[0]))) {
        throw("Push overflows stack");
    }
    data[count] = item;
    ++count;
}
/********************************************************
 * stack::pop -- get an item off the stack.             *
 *                                                      *
 * Warning: We do not check for stack underflow.        *
 *                                                      *
 * Returns                                              *
 *      The top item from the stack.                    *
 ********************************************************/
inline int stack::pop(  ) throw(bound_err)
{
    // Stack goes down by one
    --count;

    if ((count < 0) ||
           (count >= sizeof(data)/sizeof(data[0]))) {
        throw("Pop underflows stack");
    }
    // Then we return the top value
    return (data[count]);
}
static stack test_stack;        // Define a stack for our bounds checking

/********************************************************
 * push_a_lot -- Push too much on to the stack          *
 ********************************************************/
static void push_a_lot(  ) {
   int i;       // Push counter

    for (i = 0; i < 5000; i++) {
        test_stack.push(i);
    }
}

int main(  )
{
    try {
       push_a_lot(  );
    }
    catch (bound_err& err) {
       cerr << "Error: Bounds exceeded
";
       cerr << "Reason: " << err.what << '
';
       exit (8);
    }
    catch (...) {
       cerr << "Error: Unexpected exception occurred
";
       exit (8);
    }
    return (0);
}

Exceptions and Destructors

Let’s say we want to make sure that the stack is empty when it’s destroyed. So we write a destructor for our stack class:

// This is not a good idea
inline stack::~stack(  ) {
    if (count != 0)
        throw(bound_err("Stack is not empty"));
}

This sort of code contains a hidden trap waiting to be sprung. Let’s suppose that we push a few items on the stack and then execute some unrelated code that causes an exception to be thrown. The sequence of events is:

  1. Create a stack variable.

  2. Push items on it.

  3. Execute unrelated code.

  4. Throw an exception.

  5. The exception is not handled in the current procedure, so the exception logic destroys all the variables declared in the current procedure. This includes the stack variable.

  6. The destructor for the stack variable is called by the exception code. This causes a second exception to be thrown.

So what happens when an exception is thrown inside an exception? The answer is that the program terminates, so it’s not a good idea to throw an exception in a destructor.

Exceptions Versus assert

When a problem occurs, the assert statement prints out an error message and aborts the program. This error message contains the condition that failed, as well as the file name and line number where the problem occurred. This is very useful to a maintenance programmer, but not that useful to an end user.

asserts can also be compiled out of “production” code by using the -DNDEBUG option.

The assert statement is useful for checking for conditions that should never happen. The purpose of the assert is to abort the program as soon as a problem is detected.

Exceptions, on the other hand, are designed to handle conditions that are rare, but expected to occur. These occasional problems can then be handled by the program in a nice way. For example, if we attempt to push too much information on the stack, the program can simply abort that operation and continue with the next step, instead of aborting the entire program.

In general, if you don’t know what to do with the error, check it with an assert. If you think that someone may want to handle it, throw an exception.

Programming Exercises

Exercise 22-1: Add code to the queue class of Exercise 13-3 that will trigger an exception when too many items are put in the queue.

Exercise 22-2: Take the fraction class from Exercise 18-1 and add code to generate an exception when a divide-by-zero occurs. In addition, add code to generate an exception when a bad number is read.

Exercise 22-3: Update the checkbook class of Exercise 13-2 so it generates an exception when your balance goes below zero.

Exercise 22-4: Write a function count_letter that takes a single character. This function will count the number of consonants and vowels. If a nonletter is given to the function, it generates an exception.

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

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