CIS Logo SVC Logo

   Computing & Information Systems
   Department

 

Schoology Facebook        Search CIS Site      Tutorials

Software Design Using C++



C++ Exceptions


There's an exception to every rule.


What Are Exceptions?


In these Web pages we typically handle error conditions by detecting them with some sort of if test and then taking appropriate action when the condition is true. Often the action is to print an error message and exit the program. An alternate way to handle error conditions is to "throw" an exception when some error condition becomes true. One then "tries" any code that might cause such an exception, "catching" any exception that was raised, and taking appropriate action when an exception is caught. An example will help to clarify things:

A Simple Example

Take a look through the code for the above program. Note how the PrintSequence function contains what appears to be an infinite loop. Of course, it is not usually sensible to use an infinite loop, even if one has a way to jump out of the middle of it. A much more sensible way to print a sequence of numbers, which is what this function does, is to use an ordinary for loop. So, this example was invented just to illustrate exceptions, not to give a practical way to print a sequence of numbers. As for the infinite loop in the example, what saves the day is the following section of code:


if (Num >= StopNum)
   throw Num;

If the variable Num becomes too big we get out of the loop by throwing an exception. The most important thing about the exception is that it is an integer exception. The value of the exception is the current value of Num, but sometimes the actual value isn't used for anything.

So, what happens when the exception is thrown? It depends on whether or not we have any code that catches the exception. In this program we do. It is found in the main function:


try
   {
   PrintSequence(20);
   }
catch(int ExNum)
   {
   cout << "Caught an exception with value: " << ExNum
      << endl;
   }

The main function uses the "try" construct to enclose the call of the PrintSequence function. There could be many lines of code inside of the try section if need be. The catch is used in this case to catch any integer exception that might be generated. The variable ExNum will be given the integer value given to the exception by the throw statement. So, when Num becomes too big and an integer exception is generated inside PrintSequence, control is passed to the statement(s) inside the catch and then proceeds to whatever follows the "try...catch..." construct. In our case a message is printed. The value of ExNum doesn't have to be used unless you want to.

Note that if any other type of exception were generated (say a float or a pointer to a char), the above catch would not catch the exception. An uncaught exception would cause the program to terminate. Our catch was for integer exceptions only. If you want to catch all possible types of exceptions you can use the following:


try
   {
   PrintSequence(20);
   }
catch(...)
   {
   cout << "Caught an exception" << endl;
   }

You can also catch various types of exceptions separately. For example, you might use:


try
   {                                                                           
   PrintSequence(20);                                                          
   }                                                                           
catch(float FloatNum)
   {                                                                           
   cout << "Caught an exception with float value: " << FloatNum
      << endl;                                                           
   }                                                                           
catch(int IntNum)
   {                                                                        
   cout << "Caught an exception with int value: " << IntNum
      << endl;                                                           
   }                                                                           
catch(...)   // catch anything else
   {                                                                           
   cout << "Caught an exception with type not int or float" << endl;
   }                                                                           

Exceptions in an Array-Based Stack


Let's look at a more practical example of where we might use exceptions. The following is a revised version of our earlier array-based stack program. Exception handling has been used to handle the various error cases that might occur.
  • itemtype.h
  • stack.h
    Header file that sets up the abstract base class for stacks.
  • arrstack.h
    Header file that sets up the derived class for array-based stacks.
  • arrstack.cpp
    Revised to throw an exception if push or pop cannot complete.
  • reverse.cpp Revised to catch an exception generated by a push operation.
As you can see in the next to the last file, if there is no room to push an item, an integer exception is thrown. Similarly, if there is no item to pop, that operation generates an integer exception. In the reverse.cpp program, the call to Push is inside of a try statement. If an integer exception is caught, a warning message is printed to tell the user that there was no room to push the item. Although it would be possible to use a try...catch... construct with the Pop operation, no exception will be generated by this code since it is inside of a while (! Stack.Empty()) loop. Thus no attempt is made to pop from an empty stack.

Exceptions in a List-Based Stack


A more involved example is provided by revising our old list-based stack program to use exception handling.
Let's look first at the list.cpp file. In particular, let's look at the GetNode function. Part of the code for this function is repeated below:


NodePtr = new ListNodeClass(Item, NextPtr);

if (NodePtr == NULL)
   {
   throw "Cannot allocate memory";
   }

Note that the value of the exception is a literal string. It is stored as a character array. Remember that an array name is seen as a pointer to the first character in the array. Thus, the type of the exception is seen as char *.

Warning: Check your compiler to see how the new operation behaves when it cannot allocate space. Look in your documentation or try the help menu. According to the most recent C++ standard as of this writing, it should throw an exception of type bad_alloc, but Visual C++ (as of version 6.0) apparently just returns a value of NULL, which is what many other compilers do as well.

In the same file, look at the ListClass constructor. It allocates a dummy node for the front of the list. If an exception of type char * is raised when trying to do this, an error message is printed that contains the (string) value of the exception and the program is exited. Of course, the string printed is the literal string used when the exception is thrown by GetNode: "Cannot allocate memory". The choice to exit the program was made because it seemed to be a rather serious error if we cannot even allocate space for the dummy node of a list.

The InsertFront function uses GetNode, as shown below, to create a new node holding the data to be inserted. (The InsertRear and InsertInOrder functions also do this.) If an exception of type char * is caught, then we print an error message containing the (string) value of the exception and re-throw the exception. Re-throwing the exception is accomplished by the throw statement below. Whatever called InsertFront should try to catch this exception and take appropriate action. (If not, the program will terminate.)


try
   {
   NodePtr = GetNode(Item, Front->Next);
   }
catch(char * Str)
   {
   cout << "Error:  Could not insert at front -- " << Str
      << endl;
   throw;
   }

The RemoveFront function checks the count of how many items are on the list before trying to remove. If that number is zero, it throws an exception. Note that this one is an integer exception. The relevant section of code follows:


if (Count == 0)
   {
   throw Count;
   }

Next, look at the lststack.cpp file. It uses InsertFront to implement the Push operation, and so needs to check if InsertFront has thrown an exception. If a char * exception is raised, Push prints an error message. However, we have chosen not to exit the program, thinking that perhaps this error is not fatal and that the user might want to continue in spite of the error.

In the same file you see the Pop function. It checks for an integer exception, since that is the type that might be raised by the RemoveFront function that is called by Pop. If such an exception is caught, a warning message is printed.

We could also have chosen to re-throw the exception if either Push or Pop catches an exception. Then the reverse.cpp application program would need to contain code to catch such exceptions, much like we did in the above array-based stack program. It seemed to be sufficient to let it go as is.

References:


There is a lot more to exception handling than has been explained here. See a good reference book, such as one of the following, for more details.
  • C++ from the Ground Up, 2nd ed. Herbert Schildt. Osborne/McGraw-Hill (1998).
  • Teach Yourself C++, 5th ed. Al Stevens. MIS:Press (1997).
  • C++ How to Program, 3rd ed. Deitel & Deitel. Prentice-Hall (2001).

Related Item


Stacks

Back to the main page for Software Design Using C++

Author: Br. David Carlson with contributions by Br. Isidore Minerd
Last updated: October 02, 2009
Disclaimer