C++ Exceptions
(First part based on http://cis.stvincent.edu/html/tutorials/swd/except/except.html)
There's an exception to every rule.
What Are Exceptions?
In languages without exceptions (e.g., C), 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 exception is thrown by using the throw keyword from inside the try block. Exception handlers are declared with the keyword catch, which must be placed immediately after the try block.
A Simple Example
- // File: except.cpp
- #include <iostream>
- using namespace std;
- /* Given: StopNum The stopping number to be used.
- Task: To print successive integers 1, 2, 3, ... StopNum - 1.
- Throws an integer exception when StopNum is reached.
- Return: Nothing.
- */
- void PrintSequence(int StopNum) {
- int Num;
- Num = 1;
- while (true) {
- if (Num >= StopNum)
- throw Num;
- cout << Num << endl;
- Num++;
- }
- }
- int main(void) {
- try {
- PrintSequence(20);
- } catch(int ExNum) {
- cout << "Caught an exception with value: " << ExNum << endl;
- }
- return 0;
- }
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;
- }
Multiple handlers (i.e., catch expressions) can be chained; each one with a different parameter type. Only the handler whose argument type matches the type of the exception specified in the throw statement is executed.
If an ellipsis (...) is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers:
- try {
- // code here
- }
- catch (int param) { cout << "int exception"; }
- catch (char param) { cout << "char exception"; }
- catch (...) { cout << "default exception"; }
In this case, the last handler would catch any exception thrown of a type that is neither int nor char.
After an exception has been handled the program, execution resumes after the try-catch
block, not after the throw statement!.
It is also possible to nest try-catch
blocks within more external try blocks. In these cases, we have the possibility that an internal catch block forwards the exception to its external level. This is done with the expression throw; with no arguments. For example:
- try {
- try {
- // code here
- }
- catch (int n) {
- throw;
- }
- }
- catch (...) {
- cout << "Exception occurred";
- }
Exceptions in a List-Based Stack
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 *
.
std::nothrow constant
extern const nothrow_t nothrow;
This constant value is used as an argument for operator new and operator new[]
to indicate that these functions shall not throw an exception on failure, but return a null pointer instead.
By default, when the new operator is used to attempt to allocate memory and the handling function is unable to do so, a bad_alloc
exception is thrown. But when nothrow
is used as argument for new
, it returns a null pointer instead.
This constant (nothrow
) is just a value of type nothrow_t
, with the only purpose of triggering an overloaded version of the function operator new (or operator new[]
) that takes an argument of this type.
Example:
- // nothrow example
- #include <iostream> // std::cout
- #include <new> // std::nothrow
- int main () {
- std::cout << "Attempting to allocate 1 MiB...";
- char* p = new (std::nothrow) char [1048576];
- if (p==0) std::cout << "Failed!\n";
- else {
- std::cout << "Succeeded!\n";
- delete[] p;
- }
- return 0;
- }
Continuing with list example
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.
Defining custom exceptions
The C++ standard library provides a base class specifically designed to declare objects to be thrown as exceptions. It is called std::exception
and is defined in the <exception>
header. This class has a virtual member function called what that returns a null-terminated character sequence (of type char *
) and that can be overwritten in derived classes to contain some sort of description of the exception.
- // using standard exceptions
- #include <iostream>
- #include <exception>
- class MyException: public std::exception
- {
- virtual const char* what() const throw()
- {
- return "My exception happened";
- }
- } myex;
- int main () {
- try
- {
- throw myex;
- }
- catch (std::exception& e)
- {
- std::cout << e.what() << std::endl;
- }
- return 0;
- }
Output:
My exception happened.
We have placed a handler that catches exception objects by reference, therefore this catches also classes derived from std::exception
, like our myex
object of type MyException
.
All exceptions thrown by components of the C++ standard library throw exceptions derived from this exception class. These are:
Exception | Description |
bad_alloc | thrown by new on allocation failure |
bad_cast | thrown by dynamic_cast when it fails in a dynamic cast |
bad_exception | thrown by certain dynamic exception specifiers |
bad_typeid | thrown by typeid |
bad_function_call | thrown by empty function objects |
bad_weak_ptr | thrown by shared_ptr when passed a bad weak_ptr |
More complete list at http://www.cplusplus.com/reference/exception/exception/
Also deriving from exception, header <exception>
defines two generic exception types that can be inherited by custom exceptions to report errors:
Exception | Description |
logic_error error | related to the internal logic of the program |
runtime_error | error detected during runtime |
A typical example where standard exceptions need to be checked for is on memory allocation:
- // bad_alloc standard exception
- #include <iostream>
- #include <exception>
- using namespace std;
- int main () {
- try
- {
- int* myarray= new int[1000];
- }
- catch (exception& e)
- {
- cout << "Standard exception: " << e.what() << endl;
- }
- return 0;
- }
The exception that may be caught by the exception handler in this example is a bad_alloc
. Because bad_alloc
is derived from the standard base class exception, it can be caught (capturing by reference, captures all related classes).
Useful optional reading: Top 15 exception-handling mistakes and how to avoid them.