Exceptions



Errors that occur in programs are of two types:
  1. Programming errors -- You have to fix these before the program will compile and run.
  2. Run-time errors -- An error in functionality or a program crash because of something you did not anticipate. Frequently these are caused by the user doing something you did not expect. Run time errors are known as Exceptions.


Exception Handling

This provides a way of handling these run-time errors.

Some things to note about how try-catch works

  1. If no exception is thrown in a try block then all the catch blocks are ignored and execution continues after the last catch.
  2. If an exception is thrown then the remaining statements in a try block are ignored.
  3. If an exception is thrown the program searches the catch blocks in order for one whose parameter type matches the thrown exception.
  4. The last catch block should have an ellipses (...) as the parameter (remember variable argument lists?). This will catch any exception not caught in any of the other catch blocks.
  5. Surprisingly many math errors (like division by zero) are not defined in the C++ standard as exceptions therefore they will not be thrown and caught in the default catch block.

Parameters in catch blocks

  1. There can be only one in each catch block. For examples:
    catch(int x) { // Error handling code goes here. }
    catch(char *str) { // Error handling code goes here. }
    catch(struct simple *s) { // Error handling code goes here. }
    
  2. The parameter does not actually have to be named. You can just use the data type indicating the catch that should handle the exception. If the parameter is named then a value can be passed to the catch.
  3. You can also create your own unique data types for catch types (see Bummer example below).
    #include<iostream>
    #include <string>
    #include <stdexcept>
    using namespace std;
    // Prototypes of functions to call that throw an exception
    void foo();
    void bar();
    
    // Define a class as a data type exception to be thrown
    class Bummer{}; 
    // Define another class as a data type exception to be thrown
    class Oops
    {
       public:
         char msg[64];
       Oops(char *m) // Class constructor
       {
          strcpy(msg,m);
       }
    };
    
    int main(void)
    {
    	//-------------------------------------------------------------------
    	// Demonstrate different error checks with user defined error classes
    	//-------------------------------------------------------------------
    	try
        {
          cout << "\nCalling foo()\n";
          foo();
          cout << "Return from foo()\n";
        } // end try block
        catch(Bummer)	// Parameter is only a data type
        {
          cout << "Catching Bummer\n";
        }
    
    	try
        {
          cout << "\nCalling bar()\n";
          bar();
          cout << "Return from bar()\n";
        } // end try block
        catch(Oops opp)
        {
          cout << "Catching Oops\n";
          cout << opp.msg << endl;
        }
    }
    //------ Function Foo ---------
    void foo()
    {
       int error = 1;
      if(error)
      {
        cout << "Throwing Bummer\n";
        throw Bummer();
      }
    }
    
    //------ Function Bar ---------
    void bar()
    {
       int error = 1;
       if(error)
       {
          cout << "Throwing Oops \n";
    	  Oops *oops = new Oops("That was an oops!");
          throw *oops; // Throw the instance not the pointer
       }
    }
    
  4. You can catch unknown or unexpected exceptions by using the generic argument of ... . Place this catch at the end of the list of catches to handle any other exception. Note the ellipsis in the parentheses. All errors not named will fall here. You do NOT need a throw to get here.
    catch(...) { // Error handling code goes here.}
    

The throw statement

When an exception is detected a throw statement is required along with a data type that matches a parameter type of a catch. This causes execution to jump to the catch to handle the error. The throw statements go within the try block. Executing a throw statement is said to raise an exception.

When a throw is executed it also deletes all objects created within the try block and calls their destructor.

C++ Exception Classes

There are quite a few C++ exception classes that are all subclasses of class exception and are defined in stdexcept.h. All of these classes override the base class function what() which returns an appropriate string.
Below is a simple program which explicitly throws two examples of the exception classes. The command prompt image shows the results of running this program.
#include<iostream>
#include <string>
#include <stdexcept>
using namespace std;

int main(void)
{
	//---------------------------------------------------------------
	// Demonstrate different error checks defined in stdexcept,
	//  specifically length_error and domain_error
	//---------------------------------------------------------------
	// Demonstration of length_error
	try
	{
		throw length_error("Length error message.");
	}
	catch(length_error& e)
	{
		cout << "Caught demo of length_error\n\twhat: " << e.what() << endl;
	}
	// Demonstration of domain_error
	try
	{
		throw domain_error("Domain error message.");
	}
	catch(domain_error& e)
	{
		cout << "Caught demo of domain_error\n\twhat: " << e.what() << endl;
	}
	// etc. for out_of_range, invalid_argument, range_error, 
	// overflow_error and underflow_error
	//===========================================================
}

Re-throwing an exception

There are some situations in which you may call a function containing a try-catch block and an exception occurs in the function, but it is not able to fully handle the exception. In this case you may need to re-throw the exception and let the caller further process the exception.

Note in the example below that you can also specify the exceptions a function can re-throw with syntax such as:
void throwExceptFunc(int a) throw (int, string, Bummer, Oops)
Now if throwExceptFunc() is unable to handle an exception in its' try-catch, and this is usually done in the catch(…) block it can re-throw the exception back to the calling function as in the sample code shown below.
#include<iostream>
#include <string>
#include <stdexcept> 

using namespace std;
// Prototypes of functions to call that throw an exception
void foo();
void bar();

// Define a class as a data type exception to be thrown
class Bummer{}; 
// Define another class as a data type exception to be thrown
class Oops
{
   public:
     char msg[64];
   Oops(char *m) // Class constructor
   {
      strcpy(msg,m);
   }
};
// Prototypes of functions to call that throw an exception
void throwExceptFunc(int a) throw (int, string, Oops);

int main(void)
{
	//---------------------------------------------------------------
	// Demonstrate different error checks from a function defined
	//  to throw exceptions.
	//---------------------------------------------------------------
	for(int i=0; i<4; i++)
	{
		try
		{
		  cout << "Calling throwExceptFunc(" << i << ")\n";
		  throwExceptFunc(i);
		  cout << "Return from throwExceptFunc()\n";
		} // end try block
		catch(int)	// Parameter is only a data type
		{
		  cout << "Catching int exception\n\n";
		}
		catch(string str)
		{
		  cout << "Catching string " << str << endl << endl;
		}
		catch(Oops opp)
		{
		  cout << "Catching Oops: "<< opp.msg << endl << endl;
		}
		catch(...)
		{
		  cout << "Catching unnamed exception\n\n";
		}
	}
}
//------ Function Foo ---------
void foo()
{
   int error = 1;
  if(error)
  {
    cout << "Throwing Bummer\n";
    throw Bummer();
  }
}

//------ Function Bar ---------
void bar()
{
   int error = 1;
   if(error)
   {
      cout << "Throwing Oops \n";
	  Oops *oops = new Oops("That was an oops!");
      throw *oops; // Throw the instance not the pointer
   }
}

//-------------------------------------------------------------
// Function to re-throw exceptions
//-------------------------------------------------------------
void throwExceptFunc(int a) throw (int, string, Oops)
{
   try
   {
      if(a == 0) throw a;
      else if (a == 1) throw string("String exception");
      else if(a == 2) throw Oops("Oops exception");
      else throw runtime_error("Run time error");
   }
   catch(...) // catch everything here
   {
      throw; // re-throw same error back to main
   }
}