22.1 Adding Exceptions to the Stack Class

I l @ ve RuBoard

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.

22.1.1 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 }; 

22.1.2 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\n"   ;   std::cerr << "Reason: " << what_happened.what << '\n'; } 

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\n"; } 

22.1.3 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 <assert.h> 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\n";        cerr << "Reason: " << err.what << '\n';        exit (8);     }     catch (...) {        cerr << "Error: Unexpected exception occurred\n";        exit (8);     }     return (0); } 

22.1.4 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.

I l @ ve RuBoard


Practical C++ Programming
Practical C Programming, 3rd Edition
ISBN: 1565923065
EAN: 2147483647
Year: 2003
Pages: 364

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