C++ Exceptions

Introduction

Exceptions are considered unusual or unexpected occurrences during the execution of a function or a method. An example of exceptions is the inability to open files. Exceptions are differentiated from runtime errors as runtime errors are detected not by the application program but by the operating system and the operating system may then abnormally terminate the execution of the program. Examples of runtime errors include stack overflow, segmentation fault, divide by zero, etc.


Traditionally in C++, exceptions detected within a function have been expressed as integer values and returned to the calling function for handling. For example, consider the following program. The function addValue is used to append an integer value at the end of a fixed length integer array. If the index of the array exceeds its length, enumerated value ARRAY_OUT_OF_BOUNDS is returned. Otherwise enumerated value OK is returned.


#include <iostream>

using namespace std;

#define ARRAY_SIZE 5

enum STATUS
{
	OK,
	ARRAY_OUT_OF_BOUNDS
};

STATUS addValue(int* data, int* index, int length)
{
	int temp;
	STATUS reply;
	cout << "Enter an integer number:";
	cin >> temp;
	if ((*index >= length) || (*index < 0))
	{
		reply = ARRAY_OUT_OF_BOUNDS;
	}
	else
	{
		data[*index] = temp;
		(*index)++;
		reply = OK;
	}
	return reply;
}

void printArray(int* data, int length)
{
	int i;
	for (i=0;i<length;i++)
	{
		if (i>0)
		{
			cout << ",";
		}
		cout << data[i];
	}
	cout << endl;
}

int main(void)
{
	int data[ARRAY_SIZE];
	int length = sizeof(data)/sizeof(int);
	char moreFlag = 'y';
	int index = 0;
	
	while (moreFlag != 'n')
	{
		if (addValue(data, &index, length) == ARRAY_OUT_OF_BOUNDS)
		{
			cout << "Array out of bounds" << endl;
		}
		cout << "Enter 'y' for more, 'n' to exit";
		cin >> moreFlag;
	}
	printArray(data, index);
	return 0;
}


The traditional exception raising and handling mechanism described above is not formally defined and can tend to be implemented differently by different programmers. It can also subsequently lead to relatively unstructured exception handling code. Furthermore, this traditional mechanism of notifying and handling exception is restrictive as some exceptions may be raised in constructors that do not return any value.


Exception Management in C++


C++ provides an object-oriented mechanism for exception raising and handling. Exceptions are thrown by the function detecting the exception and are caught by the calling function. Exception values and variables do not form part of the function arguments and return values. Furthermore, exceptions can be represented by a variety of data types, from primitive to user defined. This facilitates encapsulating necessary information required by the exception handlers for handling specific exceptions. The above program is rewritten below using the C++ exception management facilities.


#include <iostream>

using namespace std;

#define ARRAY_SIZE 5

enum STATUS
{
	OK,
	ARRAY_OUT_OF_BOUNDS
};

int addValue(int* data, int index, int length) throw(STATUS)
{
	int temp;
	cout << "Enter an integer number:";
	cin >> temp;
	if ((index >= length) || (index < 0))
	{
		throw(ARRAY_OUT_OF_BOUNDS);
	}
	else
	{
		data[index] = temp;
		index++;
	}
	return index;
}

void printArray(int* data, int length)
{
	int i;
	for (i=0;i<length;i++)
	{
		if (i>0)
		{
			cout << ",";
		}
		cout << data[i];
	}
	cout << endl;
}

int main(void)
{
	int data[ARRAY_SIZE];
	int length = sizeof(data)/sizeof(int);
	char moreFlag = 'y';
	int index = 0;
	
	while (moreFlag != 'n')
	{
		try
		{
			index = addValue(data, index, length);
		}
		catch (STATUS exp)
		{
			if (exp == ARRAY_OUT_OF_BOUNDS)
			{
				cout << "Array out of bounds" << endl;
			}
		}
		cout << "Enter 'y' for more, 'n' to exit";
		cin >> moreFlag;
	}
	printArray(data, index);
	return 0;
}


The line

int addValue(int* data, int index, int length) throw(STATUS)
specifies that the function addValue throw an exception of type STATUS. Omitting STATUS from the parenthesis would have specified that exceptions are not thrown. Considering the following code segment in the function addValue
if ((index >= length) || (index < 0))
{
	throw(ARRAY_OUT_OF_BOUNDS);
}

the value of index is tested and if this value is outside the bounds of the array, the enum value ARRAY_OUT_OF_BOUNDS is thrown as an exception.


In the main function, the call to the addValue function is encapsulated with the try{} block. Execution of statements within a try block is attempted. If any of the statements within a try block throws an exception, it is caught by a corresponding catch{} block and the value of the exception is stored in the variable specified in the parenthesis after the catch keyword. Within the catch block, this value can be manipulated. The catch block is referred to as exception handler for exceptions of the type referred to in the parenthesis after the catch keyword. In the above example, the catch block is exception handler for exceptions of the type STATUS.


Ways to Catch an Exception

Whenever an exception is thrown, the exception object being thrown is copied and the copy is passed to the catch block in the calling function. The original exception object, if automatically declared is destroyed after the copy is made and is thrown. Catching them by value can have several drawbacks. Firstly, a double copy operation takes place. Secondly, if the thrown object is caught as value by a base class exception object, slicing occurs, i.e., the derived class's members are inaccessible.

An exception can be thrown as a pointer to the exception object. Because the original exception, if automatically declared, is destroyed the caught pointer points to the destroyed object. Alternatively, the exception object can be statically or globally declared if its pointer has to be thrown. Furthermore, the exception object can be dynamically created if its pointer has to be thrown but it has to be destroyed explicitly in the exception handler.

The most appropriate way to catch an exception is, therefore, by a reference. Only a single copy operation takes place when an exception is caught by reference. Furthermore, slicing does not take place.

Exception Classes

As mentioned earlier, exception values and variables can be from a variety of data type, primitive as well as user defined. Exception attributes can be encapsulated in objects of classes representing specific exceptions. References to objects of these classes can be thrown and then manipulated in the respective exception handlers. The above example is re-coded as follows. Here an exception class is used to represent the array out of bounds exception.

#include <iostream>
#include <string>

using namespace std;

#define ARRAY_SIZE 5

enum STATUS
{
	OK,
	ARRAY_OUT_OF_BOUNDS
};

class ArrayException
{
	private:
		STATUS exceptionCode;
		string description;
		
	public:
		ArrayException(STATUS code, const string& desc)
		{
			exceptionCode = code;
			description = desc;
		}		
		
		STATUS getCode(void) const
		{
			return exceptionCode;
		}
		
		const string& getDescription(void) const
		{
			return description;
		}
};

int addValue(int* data, int index, int length) throw(ArrayException)
{
	int temp;
	cout << "Enter an integer number:";
	cin >> temp;
	if ((index >= length) || (index < 0))
	{
		ArrayException exp(ARRAY_OUT_OF_BOUNDS,
                         "Array out of bounds exception");
		throw(exp);
	}
	else
	{
		data[index] = temp;
		index++;
	}
	return index;
}

void printArray(int* data, int length)
{
	int i;
	for (i=0;i<length;i++)
	{
		if (i>0)
		{
			cout << ",";
		}
		cout << data[i];
	}
	cout << endl;
}

int main(void)
{
	int data[ARRAY_SIZE];
	int length = sizeof(data)/sizeof(int);
	char moreFlag = 'y';
	int index = 0;
	
	while (moreFlag != 'n')
	{
		try
		{
			index = addValue(data, index, length);
		}
		catch (const ArrayException& exp)
		{
			if (exp.getCode() == ARRAY_OUT_OF_BOUNDS)
			{
				cout << exp.getDescription() << endl;
			}
		}
		cout << "Enter 'y' for more, 'n' to exit";
		cin >> moreFlag;
	}
	printArray(data, index);
	return 0;
}
It is possible for a function to throw exceptions of more than one exception type (only one at a time). All of these classes have to be specified in the parenthesis after the throw keyword in the function declarator. Separate catch sections is used for handling exceptions of different classes. Consider the following program. It expects the user to enter a string between 10 and 20 characters. The function throws a ShortStringException if a string of less than 10 characters is entered and a LongStringException if the length of the string entered is more than 20 characters. In the main function handlers for both these exceptions types are specified.

#include <iostream>
#include <string>

using namespace std;

class ShortStringException
{
	private:
		string desc;
	public:
		ShortStringException(void)
		{
			desc = "Short String Exception";
		}
		const string& getDescription(void) const
		{
			return desc;
		}
};

class LongStringException
{
	private:
		string desc;
	public:
		LongStringException(void)
		{
			desc = "Long String Exception";
		}
		const string& getDescription(void) const
		{
			return desc;
		}
};

const string getString(void) throw(ShortStringException, LongStringException)
{
	string data;
	cout << "Enter string between 10 and 20 characters:";
	cin >> data;
	if (data.size() < 10)
	{
		ShortStringException bExp;
		throw bExp;
	}
	if (data.size() > 20)
	{
		LongStringException lExp;
		throw lExp;
	}
	return data;
}

int main(void)
{
	try
	{
		string data = getString();
		cout << data << endl;
	}
	catch (const ShortStringException& bExp)
	{
		cout << bExp.getDescription() << endl;
		cout << "Should have entered something longer." << endl;
	}
	catch (const LongStringException& lExp)
	{
		cout << lExp.getDescription() << endl;
		cout << "Should be more economical with words." << endl;
	}
	return 0;
}

Standard Exception Classes

Exceptions thrown by the standard language or the standard library are derived from the base class exception which is extended to provide 3 groups of standard exception classes shown in the following figure. These groups provide exceptions for language support, exceptions for the C++ standard library and exceptions for errors outside the scope of a program. These exceptions are briefly described in the table following the figure. 

Exception Group Description Base Class Subclasses Description
Exceptions for language support These classes are used by the language features to indicate errors and exceptional conditions. exception bad_alloc Thrown when the new operator fails.
exception bad_cast Thrown by the dynamic_cast operator if a type conversion on a reference fails at runtime.
exception bad_typeid Thrown by the typeid operator for runtime type identification. If the argument to typeid is null or 0, this exception is thrown.
exception bad_exception Used to handle unexpected exceptions. If an exception is not specified in the throw clause of the function declarator and is thrown in the function, unexpected() function is called which terminates the program. If std::bad_exception is specified in the throw clause then any unexpected exception is replaced by bad_exception.
Exceptions for the standard library These exception classes are mostly derived from the logic_error class which is derived from the exception class. These are raised as a result of logic errors that can be avoided by the program.  logic_error out_of_range Used to report a value that lies outside the expected range of values for the variable.
logic_error length_error Used to report an operation that would result in a value exceeding the size of the variable used to hold it. 
logic_error invalid_argument Used to report invalid arguments supplied to a function.
logic_error domain_error Used to report a domain error.
exception ios_base::failure Thrown by the IO libraries to report an error or an end of file.
Exceptions for errors outside the scope of a program These exception classes are derived from the runtime_error class and are used to report errors that are beyond the scope of the program and are not easily detectable. runtime_error range_error Used to report range errors in internal computation.
runtime_error overflow_error Used to report an arithmetic overflow.
runtime_error underflow_error Used to report an arithmetic underflow.

Most of these exception classes are defined in stdexcept header file. The following table lists the exception classes that are not defined in stdexcept. Header files in which these are defined are also specified.

Exception Class Header File
exception exception
bad_exception exception
bad_alloc new
bad_cast typeinfo
bad_typeid typeinfo
ios_base::failure ios

The following program demonstrates the use of length_error.

#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <string>

using namespace std;

#define ARRAY_LENGTH 10

void populateArray(int* intArray)
{
int i;
for (i=0;i<ARRAY_LENGTH;i++)
	{
		intArray[i] = rand();
	}
}

void printArray(int* intArray)
{
	int i;
	for (i=0;i<ARRAY_LENGTH;i++)
	{
		cout << "[" << i << "] = " << intArray[i] << endl;
	}
}

int getValue(int index,int* intArray) throw (length_error)
{
	int value;
	if ((index < 0) || (index >= ARRAY_LENGTH))
	{
		throw length_error("Out of array index exception");
	}
	else
	{
		value = intArray[index];
	}
	return value;
}

int main(void)
{
	string input="";
	int intArray[ARRAY_LENGTH];
	populateArray(intArray);
	printArray(intArray);
	while (input != "END")
	{
		cout << "Enter index: ";
		cin >> input;
		if (input != "END")
		{
			try
			{
				cout << getValue(atoi(input.c_str()),intArray) << endl;
			}
			catch (exception& exp)
			{
				cout << exp.what() << endl;
			}
		}

	}

	return(0);

}
This program initialises an array of 10 integers with random numbers and then prompts the user to enter the index to display the value of the array element at that index. If the user entered index outside the array bounds, the function getValue(int index, int* intArray) throws a length_error exception initialised to "Out of array index exception". This exception is caught by the calling (main) function where this message is retrieved using the what() method of length_error class (or exception base class) and then displayed on the console. The output of a session of this program is shown in the following figure.


Extending Standard Exception Classes

The standard exception classes mentioned above may provide restricted exception handling support in certain cases. Application programmers may want to extend appropriate standard exception classes to include further information about the exception event. The following program shows a class MyLengthError that extends the standard length_error exception. The constructor of this class takes the current index and the length of the array as arguments. This class also overrides the what() method and provides a more comprehensive error message by including the current index value and the size of the array in it.

 

#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <cstdio>
#include <string>

using namespace std;

#define ARRAY_LENGTH 15

class MyLengthError:public length_error
{
private:
	int currIndex;
	int length;
	char message[256];

public:
	MyLengthError(int currentIndex, int arrayLength):length_error("")
	{
		currIndex = currentIndex;
		length = arrayLength;
	}

	const char* what(void) const
	{
		sprintf((char*) message,"Array out of bound exception\nArray Length = %d\nIndex Provided = %d",
				length, currIndex);
		return message;
	}
};

void populateArray(int* intArray)
{
	int i;
	for (i=0;i<ARRAY_LENGTH;i++)
	{
		intArray[i] = rand();
	}
}

void printArray(int* intArray)
{
	int i;
	for (i=0;i<ARRAY_LENGTH;i++)
	{
		cout << "[" << i << "] = " << intArray[i] << endl;
	}
}

int getValue(int index,int* intArray) throw (length_error)
{
	int value;
	if ((index < 0) || (index >= ARRAY_LENGTH))
	{
		throw MyLengthError(index, ARRAY_LENGTH);
	}
	else
	{
		value = intArray[index];
	}
	return value;
}

int main(void)
{
	string input="";
	int intArray[ARRAY_LENGTH];
	populateArray(intArray);
	printArray(intArray);
	while (input != "END")
	{
		cout << "Enter index: ";
		cin >> input;
		if (input != "END")
		{
			try
			{
				cout << getValue(atoi(input.c_str()),intArray) << endl;
			}
			catch (exception& exp)
			{
				cout << exp.what() << endl;
			}
		}
	}
	return(0);
}
The following figure shows the output of an execution session of the above mentioned program. Please note the comprehensive exception message.


Issues with Exception Handling

 

Destructors Should Not Throw Exceptions

Class destructors should not be allowed to throw exceptions. If a function called within a destructor throws an exception, it should either be swallowed (i.e., either ignored or some valid operation performed in response) or the program should be terminated. Throwing an exception from the destructor can cause premature program termination or undefined behaviour.


The former happens when the destructor has been called as a result of an exception being thrown. If an exception occurs within the destructor when another exception is active, C++ calls the terminate function which abnormally terminates the application. In case a destructor has been called during the normal execution of the program and an exception is thrown from the destructor, the object to which that destructor belongs is not completely destroyed resulting in memory and resource leaks.


Exceptions in Constructors can Cause Resource Leaks

Constructors are usually programmed to initialise resources to be used by the objects to which these constructors belong. In case an exception is thrown before a constructor has completed its execution, resources that have been initialised may not be released resulting in a resource leak. Therefore, any resource initialised or allocted during object construction should be released if an exception occurs within a constructor and that exception needs to be rethrown.


Exception Atomicity

Exception atomicity suggests that an object should remain in a well-defined and a usable state even if an exception is thrown in the midst of processing. Ideally, when an exception is thrown, the state of the object should be rolled back to the state in which the object existed before that exception was thrown. The following methods to achieve exception atomicity have been taken from Joshua Bloch's "Effective Java: Programming Language Guide" (item 46 - Strive for failure atomicity).

1. Use immutable objects. Operations performed on immutable objects do not change the state of these objects. Even if the operation fails, the object remains in its previous and valid state. In fact, immutable objects should only be created with a valid state.

2. Check input data prior to processing. The input data (input arguments to a method) should be checked prior to using them in any processing within the method. If the processing of the input data will result in the initial valid object state transforming to an invalid state, an exception should be thrown prior to this processing.

3. Catch an exception and rollback. If the input data is invalid and the operation performed by the method would result in an exception being thrown, that exception should be caught and a rollback operation should be performed to return the object to its prior valid state. However, this may not always be possible as results of several operations (e.g., invalid array index value) in C++ may go unchecked.

4. Perform operations on copies. If the desired operation is performed on a copy of the object's initial state and the operation fails, the updated (but invalid) copy may be discarded. This leaves the object in its prior valid state. If the operation is successful, the updated copy contains valid state which is then copied over to the prior object state.