Title: Exceptions and exception handling
1- Exceptions and exception handling
- Client programmers can make errors using a class
- attempting to dequeue an item from an empty
Queue - even though we have placed a precondition on the
method - Item_type Queuedequeue( void )
- // Pre Queue is not empty
- the client might not check before calling the
dequeue() - What should the dequeue() member function do?
- More generally, what should any function do when
an error arises?
2- Possible ways to handle errors
- Do nothing and run the risk of severe problems
such as a segmentation fault resulting in
abnormal program termination - Check for the error (the failing precondition)
and return a default value if necessary and run
the risk that the client programmer may never
clue into the fact that something went wrong,
leading to unexpected results (or worse) for the
client code - Check for the error condition and alert the
client that something went wrong
Option 3 is by far the best approach! How do we
inform the client that something went wrong?
3- Notifying the client
- Terminate the program and optionally print an
error message - if ( !count )
- cerr ltlt Queue is empty ltlt endl
- exit (-1)
-
- else
- not great because the client cannot recover
- Have the function return a bool indicating
successful/failure - bool enqueue( const Item_type item )
- reasonable when we know failure is expected at
some point - but not when something unexpected happens only
seldomly
4- Or we can throw an exception
- C has a special mechanism for signaling failure
- any function can throw an exception when it
detects an error - if ( count gt CAPACITY )
- throw logic_error( Queue is full )
- else
- this terminates the call to the function
- the client code then has the option of catching
the exception - catch ( exception e ) error handling
- the client might not catch the exception
- if it is not caught a default handler will
terminate the program
5- What is an exception? Is it an int? Is it a
char? - In C, any kind of value can be thrown as an
exception - usually the variable that is thrown is an object
- some are designed specifically to act as
exceptions - C defines a set of these classes in the
standard library - the declarations are in the header file
ltexceptiongt
runtime_error
6- The term exception is overloaded
- We use the term exception in three distinct
ways - as in common language to say that something
unexpected has taken place during
execution the executing program got an
exception - in computer programming to say that some part
of the program has signalled that something is
out of the ordinary the math library threw
and exception - to mean an object of the class
exception the class logic_error is a subclass
of exception
7- The what() method in the exception class
- The exception class uses this to describe the
error - what() has the following signature
- const char what() const
- what() returns a pointer to an error message
- the constructor has a string parameter for the
message - We can create our own exception classes
- they are based on logic_error and runtime_error
- a logic_error indicates the client should know
better - a runtime_error indicates external cause
- a bad_alloc from new means no memory available
8We define an exception that can be thrown when
the dequeue function is called on an empty
queue We will call this exception AccessViolation
to indicate that we are trying to access an
element that does not exist include
ltexceptiongt include ltstdexceptgt // probably
includes ltexceptiongtusing namespace stdclass
AccessViolation public logic_error
AccessViolation( void ) logic_error( Queue
is empty, cant dequeue )
This uses inheritance, which we have not covered
yet! The what() method is declared in the
exception class
9Now lets think about how our dequeue() function
will be writtenItem_type Queuedequeue( void
) if( isEmpty() ) // check if queue is
empty throw AccessViolation() //
rest of function omitted, but it does //
whatever we do if queue is not empty throw
logic_error( Queue is empty, cant dequeue
) Here we call the constructor of the
AccessViolation class to create an anonymous
instance of the class and throw it If the
exception is thrown, the dequeue() function is
terminated without executing any subsequent code
in the function
10We could also just declare a variable whose name
is AccessViolation to throw when want to signal
that a dequeue operation has been attempted on an
empty Queue include ltexceptiongt include
ltstdexceptgt // probably includes
ltexceptiongtusing namespace stdlogic_error
AccessViolation( Queue is empty, cant dequeue
) The code cout ltlt AccessViolation.what()
ltlt endl Produces the output Queue is empty,
cant dequeue
11Using this approach, our dequeue() function will
be writtenItem_type Queuedequeue( void )
if( isEmpty() ) // check if queue is empty
throw AccessViolation // rest of
function omitted, but it does // whatever we
do if queue is not empty throw logic_error(
Queue is empty, cant dequeue ) Instead of
using the constructor of a class to create an
anonymous instance of the class we throw the
variable AccessViolation that has already been
defined. This is more efficient (the constructor
is only called once, not every time) and it
avoids problems if memory allocation is not
working because there is no memory left to
allocate.
12- How the client uses the exception
- try while( true ) // Horrible!
Should be myQueue.dequeue() // checking
precondition! - catch ( exception e ) cout ltlt
Exception ltlt e.what() ltlt endl - // now continue with rest of program
- There are two subtleties here that we will not
fully explain (yet) - why the object e is a reference to an exception
- how the what() method retrieves Queue is empty,
cant dequeue
13- What this means to a client
- Any code that
- calls a function that might throw an exception
- calls a function that calls a function that
might throw an exception - etc.
- should be wrapped in try-catch blocks
- we try to execute the code in the try-block
- if problems arise we let the catch-block handle
it - This passes the responsibility for dealing with
the error condition back to the client, where it
belongs - Do not to overuse this feature!
14Some words of caution about exceptions Rule 1 If
a function can reasonably handle the error
condition itself then it should do so. An
exception should be thrown only in the case where
the function cannot be expected to handle the
situation. Rule 2 The fact that a function
throws an exception is not an excuse for the
client to write sloppy code. Remember that an
exception should be thrown only when something
exceptional occurs. Rule 3 Do not throw
exceptions to bail out from situations that you
as a programmer have difficulty resolving for
example, bailing out from a recursive call
because you cant think of any other way to do it
gracefully!
15- Example of a use of exceptions (1)
- Suppose an employee object has two data members
- name to represent the employees last name
- employeeNum - to represent the employee number
- class employee string name
- int employeeNum
- Now suppose we want to write a function to read
an employee from a file - We can run into two problems when we use the
function - we hit the end of file so there is no more data
to read - the data in the file is not a valid employee
number
16- Example of a use of exceptions (2)
- The last situation will occur if the file
contains the following data, for example - Jones 3542Wong 4214Smith - 5434Ng 6785
- one of these situations is expected (the end of
file) - and the other (invalid format) is not expected
- we handle them accordingly
- we provide a return value for end-of-file
- we throw an exception for invalid data
17- Example of a use of exceptions (3)
- bool readEmployee( ifstream inStream, employee
next ) inStream gtgt next.name if(
inStream.eof() ) return false
inStream gtgt next.employeeNum if(
inStream.fail() ) throw
BadDataError() - return true
- the end-of-file is expected at some point
- we return a bool and let the client check the
return value - finding badly formatted data in the file is not
expected - we handle this situation by throwing an exception
18- Some of the fine details
- We can have more than one catch block
- this allow us to catch different types of
exceptions - We catch any type of exception
- try // do something that might throw an
exception - catch ( AccessViolation excep ) // do
this if AccessViolation - catch ( exception excep ) // do this
if some other exception classcatch ( ... )
// do this for any other type that is thrown
19- Which catch block handles the exception?
- If an exception is thrown from the try block, it
is handled by the first catch block that can
accept it based on its type - in the previous example, if the exception that
gets thrown is an AccessViolation, the exception
is handled by the first catch block (and only
that block) - if the exception that gets thrown cannot be
handled by the first block it is tested against
subsequent catch blocks - if it cannot be handled by any of them, it is
handled by the catch-all block. - it is important to order catch blocks so that
earlier blocks handle the more specific
exceptions - the catch-all block, if there is one, must
always go last
20What if nothing catches an exception that was
thrown? The run-time system catches the
exception and the program terminates abnormally
(it aborts).