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.
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:
class Poponempty {};
throw
ing an object of this class at the point where the exception actually occurs
int Stack::pop( ) { if (empty()) throw Poponempty(); // throws an (unnamed) object created by the Poponempty default constructor .... }Note the brackets in
throw Poponempty();
If you left them out - throw Poponempty;
- you would be trying to throw a type, which is not possible. If you preferred, you could give the exception object a name, as in { Poponempty ppe; throw ppe; }
try
block in which the exception might arise. Often this will be the body of a function, perhaps main()
, for example
int main( ) try { .... x = mystack.pop(); // but mystack might be empty! .... }
catch
clause at the end of the try
block to handle the exceptions, e.g.
try { .... x = mystack.pop(); // but mystack might be empty! .... } catch (Poponempty) { cerr << "Can't pop an empty stack\n"; exit(1); }You could give the Poponempty parameter a name if you wanted -
catch (Poponempty x)
- but there would be no point in doing so since the object is empty and there is therefore no reason to refer to it within the catch clause; the object has served its purpose just by being thrown and caught.
(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.)
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".
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).
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.
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); }
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 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 }
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 }
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