Exceptions
Errors that occur in programs are of two types:
-
Programming errors -- You have to fix these before the program will compile and run.
-
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.
-
A simple way to handle run-time errors is to try to anticipate where and how they
may occur and write your code in such a way that it checks for these errors.
-
You can check for errors using the C++ assert macro as in the following code example:
#include <iostream>
#include <cassert>
#include <string>
using namespace std;
int main(void)
{
//----------------------------------------------------------
// Demonstrate division by zero error check with assert()
//----------------------------------------------------------
int dividend, divisor, quotient;
cout << "Line 2: Enter the dividend: ";
cin >> dividend;
cout << endl;
cout << "Line 5: Enter the divisor: ";
cin >> divisor;
cout << endl;
assert(divisor != 0); // Catches the error but still crashes the program
quotient = dividend / divisor;
cout << "Line 10: quotient = " << quotient << endl;
}
The only problem is that the assert macro still causes the program to
terminate if the assert argument is false.
-
A better approach to handling exceptions is to enclose code you want
to protect against run-time errors in a try-catch block. This
begins with the key word try followed by the protected code in
braces ({ }).
try
{
// Insert code to be protected here
}
Immediately after the try block place catch blocks to handle specific types
of errors. These are called by throw statements.
catch(parameter)
{
// Insert code to handle the error here
}
Some things to note about how try-catch works
-
If no exception is thrown in a try block then all the catch blocks are
ignored and execution continues after the last catch.
-
If an exception is thrown then the remaining statements in a try block are ignored.
-
If an exception is thrown the program searches the catch blocks in order for one
whose parameter type matches the thrown exception.
-
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.
-
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
-
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. }
-
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.
-
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
}
}
-
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
}
}