C++ Week 19

Exception handling

An exception is something that may happen in your program but which shouldn't really, and which makes it unsafe or impossible for the program to continue. Typical examples of exceptions are:

There are various ways of dealing with these problems. At one extreme, you can just do nothing; the C++ standard stack class, for example, produces undefined behaviour if you try to pop an empty stack object, and the subscript operator([]) for strings and vectors makes no attempt to trap array-bounds errors (though the at function is provided as an alternative, which does trap these errors). At the other extreme, you can crash the program, perhaps with an error message, e.g.

int Stack::pop()
{	if (empty())
	{	cerr << "Cannot pop empty stack" << endl;
		exit(1);
	}
	.....
}

Between these two extremes are ways of signalling the error in the hope that the problem can be sorted out elsewhere. One approach is to appeal to a user to sort out the problem. For example:

do
{	cout << "Please key in file name: ";
	cin >> filename; 
	infile.open(filename.c_str());
	if (infile.fail())
	{	cout << "Cannot open " << filename << endl;
		cout << "Please check file name and try again" << endl;
	}
} while (infile.fail());

Or you might return an error code so that the calling program can take appropriate action. If the problem is liable to arise in a procedure, you can turn it into a boolean function; returning true indicates that all went well, while false indicates a problem. Now, instead of calling a procedure as, say, r.get_record(); you call it as a function:

if (!r.get_record())	// getting a record is now a side-effect
{	Deal with problem
}
else
{	Process record
}

If you have a (non-void) function, you might be able to commandeer one of the possible return values as an error flag. If, for example, your int function always returns values >= 0, you might use -1 to indicate that an error has occurred. This assumes, of course, that -1 could never be returned as a genuine return value. Giving this role to a value that is merely unlikely (though not impossible) is asking for trouble.

An extra (reference) parameter is sometimes used, like flag in this example:

bool  flag;
x = f(y, z, flag);	// f returns int but also sets flag as side-effect
if (!flag)
{	there is a problem ....
Or a global might be used. There is in fact a standard global int variable supplied for this purpose, called errno (you have to #include <cerrno>). But it obviously suffers from the same problems as global variables in general - several functions might be setting and clearing the same variable and confusing each other in the process.

A further variation is to have a function return a pointer, say an int* rather than an int. Now the calling program can test for a NULL return value:

int* p = f(x, y);
if (p == NULL)
{	there is a problem ....
}
else
{	use *p
}

But be careful when writing the function. Can you see what is wrong with this?

int*	f(int x, int y)
{	int n;
	assign some value to n
	return &n;
}

The problem is that n is an auto variable - it ceases to exist once the function has completed. By the time the calling program comes to try to use the returned value (as *p), the part of memory that p is pointing at may no longer hold the value that f computed. It would work if n were static (remember to place an initial value in it with an assignment, unless you actually want it to start with the value it ended up with last time) or if you returned a pointer to a new int, though beware of memory leaks:

int*	f(int x, int y)
{	int* ptr = new int;	// calling program responsible for deleting it
	assign some value to *ptr
	return ptr;
}

Yet another variation is to create a class that contains both returned value and error code, such as this:

class Interr
{ public:
	....
  private:
	int x;
	bool error_flag;
};
and have your function return an Interr rather than an int.

The problem with all these error-code systems is that they assume that the writer of the calling program knows what system the function is using and what the significance of this or that error code is. If the writer of the program and the function are one and the same person, this may be acceptable, though even there the resulting program exhibits what software engineers call "tight coupling", which is when one part of a program depends overmuch on the details of another part, so that the two are not easily disentangled.

The C++ exception handling system

Consider, however, using a class whose internals are hidden from you, such as a library class. Which of the above methods would you like it to use for coping with exceptions? You probably don't want it to ignore them and behave unpredictably, and you probably don't want it to crash the whole program for reasons invisible to you. So it has to have some way of telling you that something has gone wrong in a way that enables you, possibly, to deal with it. This is the situation you should keep in mind when considering the C++ exception handling mechanism.

Briefly, it works by:

(The above syntax is correct, but on the Borland compiler you may find that you have to put in an extra '{' before the try and a matching '}' after the catch clause.)

Unwinding the stack

In the above example, the exception is thrown from a function call made directly within the try block. But it would also work if the exception was thrown from, say, a function called by a procedure called within the try block:

int func(int a)
{	....
	x = mystack.pop();
	....
}

void proc()
{	....
	y = func(z);
	....
}

int main()
try {	....
	proc();
	....
}
catch (Poponempty)
{	....
}

If pop threw Poponempty, the exception-handling mechanism would look for a catch clause to handle it. Perhaps the call in func occurs inside a try block with a suitable catch clause at the end. If not, perhaps the call to func occurs inside a try block in proc with a suitable catch clause at the end. If not, perhaps the call to proc occurs inside a try block in main with a suitable catch clause at the end. This process of moving up the hierarchy of calls is known as "unwinding the stack".

throw/catch is not exactly like parameter passing

The thing that gets thrown is usually a specially defined exception object, as in this example (Poponempty), but it can be a variable of some other type, such as an int. Be careful, however. Although the throw and catch mechanism looks like parameter passing, it is not the same thing. In particular, no type conversion takes place. catch (double d) would not catch the -1 from throw -1; and catch (string s) would not catch the "Stack empty" from throw "Stack empty"; (a string literal is, strictly, a C-string, not an object of the C++ string class).

Declaring exceptions in the interface

You can declare in the interface that a function might throw a particular exception (or give a list of exceptions that it might throw):

class Stack
{  public:
   ...
  int pop() throw (Poponempty);
   ...
};	

You put this in the function definition also:

int Stack::pop( ) throw (Poponempty)
{   if (empty())
	throw Poponempty();
    ...
}

Even if the programmer who is using this class has no access to the function definitions, he/she can still see that the pop function might throw a Poponempty exception and can incorporate a try block and a catch clause to handle it.

In fact, if a function has an exception list of this kind, it is not only warning you that it might throw one of these exceptions; it is also promising not to throw any other exceptions. A function without such a list is not making any promises - it might throw any exception.

Exception objects containing data

If your calculations increment or multiply data you may inadvertently use a value in excess of INT_MAX. To catch this you have to test the possible overflow before the calculation. It is also possible to pass arguments to the exception class to help you track errors, e.g.

if (x < INT_MAX - sum) sum += x;
else throw Integeroverflow(x, sum); // uses a two parameter constructor for the exception object

To make this code work, we need an appropriate constructor. For example:

class Integeroverflow
{  public:
      Integeroverflow(int ix, int iy): x(ix), y(iy) {}
      int get_x() const {return x;}
      int get_y() const {return y;}
   private:
      int x;
      int y;
};

Also the catch clause will now look like:

catch (const Integeroverflow& iobj) // iobj is just an identifier
{  cerr << "Integer overflow caused by " << iobj.get_x() << " and " << iobj.get_y();
   exit(1);
}

Standard exceptions

There are some standard exceptions, mostly defined in <stdexcept>. They are all derived from the same base class. They all take a C-string (e.g. a string literal) as argument to the constructor and they all have a member function called what that returns it.

A useful one to know is bad_alloc which is thrown if a call to new fails (i.e. your program requests some more memory and the operating system cannot oblige). Another is logic_error, versions of which are thrown by the STL (the Standard Template Library that contains vector etc).

You can catch these exceptions:

catch (const bad_alloc& ba)
{	cerr << ba.what() << endl;
	Deal with it, eg release some storage by deleting some parts of a structure
}

You can throw one of them with your own argument:

if (month < 1 || month > 12)
	throw logic_error("Impossible value for month");   // or could use invalid_argument, which inherits from logic_error

Or you can write your own exception class that inherits from one of them:

class Myerror : public logic_error
{ public:
	Myerror (string s, int x) : logic_error(s), mynumber(x) {}
	Data and functions specific to Myerror
};

More than one exception

More than one exception might be thrown from within a given try block, in which case you might have more than one catch clause at the end. The exceptions are tested in the order you place the catch clauses:

try {
	Various exceptions might be thrown from in here
    }
catch (Exception1)
{	Deal with Exception 1
}
catch (Exception2)
{	Deal with Exception 2
}

The order of the catch clauses - and therefore the order in which the exceptions are tested - might be important if, for example, Exception1 was a special form of Exception2 (i.e. derived from Exception2) requiring special treatment.

You can also have a "catch-all" catch clause to deal with any exceptions that you have not explicitly mentioned (the "..." is not my shorthand - it is the correct syntax for this):

catch (...)
{	Deal with any other exception
}

A catch clause is not a procedure

In the earlier examples, the only action taken in the catch clauses was the rather unexciting one of issuing an error message and exiting the program. You are not restricted to this - the catch clause can do anything you like. If your catch clause is at the end of main, then there is perhaps not a great deal more you can do except release resources, close files and generally tidy up. If the catch clause is at the end of a try block elsewhere, you might be able to take remedial action and continue with the program.

The normal flow of control in a program is that a procedure call transfers control to the procedure; at the end of the procedure, control returns to the point where the procedure was called and the program carries on from there. But a catch clause is not a procedure. At the end of the catch clause you do not return to the point where the exception was thrown and carry on from there - exception-handling in C++ is non-resumptive. At the end of the catch clause you are at the end of the try block. For example:

int f(int y)
{	....
	if (some problem)
		throw logic_error("Some problem");
//A
	....
}

void proc()
try {	....
	x = f(y);
//B
	....
}
catch(const logic_error& le)
{	....
}

int main()
{	....
	proc();
//C
	....
}

After the catch clause has executed, then, unless something in the catch clause directs otherwise, the program will resume at point C, not points A or B.

Or consider this example, where the try block is the body of a non-void function (imagine that exceptionA and exceptionB are derived from logic_error):

int f(int x)
try{	if (x == 1) throw exceptionA("A");
	if (x == 2) throw exceptionB("B");
	return 9999;
}
catch (const exceptionA& a)
{	cout << a.what() << " ";
	return 1111;  // this is the return from f
}
catch (const exceptionB& b)
{	cout << b.what() << " ";
	// no return, so f's return value is undefined
}

int main( )
{	cout << "f(1) " << f(1) << endl;
	cout << "f(2) " << f(2) << endl;
	cout << "f(3) " << f(3) << endl;
}
The output is
f(1) A 1111
f(2) B 396200  [a garbage number - could be anything]
f(3) 9999

If the exception is something you can fix, you might be able to deal with the exception and carry on by putting the whole try block inside a loop:

while(some_condition)
{   try {
           Something here might throw Some_exception
        }
    catch (Some_exception) 
    {  Fix the problem
    }
}     

Perhaps, however, you can only partially deal with the problem in the catch clause and do not want the program to resume at its default position. In this case you might rethrow the exception (simply with throw;) for another catch clause further up the hierarchy to handle:

void proc( )
try {
        Something here might throw Some_exception
    }
catch (Some_exception)
{   Do some of the handling here
    throw;    // Rethrow the exception
}

int main( )
try {   Contains, directly or indirectly, a call to proc( )
    }
catch (Some_exception)
{    Complete the handling
}

Stack unwinding and memory management

Can you see the problem with the following?

void procD()
{	Node*	head;
	// Creates a linked list
	// Some_exception thrown here
}

void procC()
try {	....
	procD();
	....
}
catch(Some_exception)
{	....
}

Presumably one of the things that the catch clause ought to do is to delete the linked list, but it can't; it has no access to head (which, being an auto variable, has already ceased to exist anyway). So we have a potential memory leak. One solution is as follows:

void procD()
try {	Node*	head;
	// Creates a linked list
	// Some_exception thrown here
}
catch(Some_exception)
{	// Delete list
	throw;	// rethrow exception for procC to deal with
}

This would work, but you can imagine that it is tiresome to write catch clauses for every block of code that might produce a memory leak. A better solution would be to package the linked-list code into a class (this is better object-oriented programming anyway):

void procD()
try {	Linked_list lst;
	// Creates a linked list
	// Some_exception thrown here
}

This solves the problem because, when an object goes out of scope, its destructor is called automatically. So, by the time procC's catch clause is executed, the linked list has already been deleted.

And this works all the way up as the stack unwinds. If procA calls procB which calls procC which calls procD, and the exception arises in procD and the catch clause that handles it is in procA, then all the objects created in procD, procC and procB will have been destroyed by the time procA's catch clause begins to execute.


Notes on R. Mitton's lectures by S.P. Connolly, edited by R. Mitton, 2000-08