Exception Handling Programmers must deal with errors and exceptional situations: User input errors...

Preview:

Citation preview

Exception Handling Programmers must deal with errors

and exceptional situations: User input errors Device errors

Empty disk space, no memory Component errors

Class might perform incorrectly invalid array index Pop() empty stack

Handling errors Most errors are unpleasant to handle The programmer should not just abort

the program with an assertion failure; the user may lose all work performed during the program session

Instead, the program must do the best it can to recover, and exit gracefully, only if it is really necessary.

Reaction to error Conditions Returning an error code

#define FILE_OPEN_ERROR 1#define OK 0

int openMyFile() {FILE * f = fopen(“File.txt”, “r”);if ( f == NULL )

return FILE_OPEN_ERROR;else

/* Do things to a file*/…return OK;

}

Advantage: simple to implementDisadvantage: Code contains a lot of error checking

Do nothing Ignore error condition

Common approach Simpler to code

Hard to debug Program might terminate or abort or produce

incorrect answer Location of original problem might be hard to find Can use assert() in the debug mode Can be misleading to the caller.

void Stack::push() will not push element when stack is full, however, the caller will think he successfully pushed element on the stack

When coping fails, can not simply ignore the problem

Setting an Error Variable You keep a global variable, such as errno Whenever function returns, you can check errno variable

if there was an error when executing the function Simple to implement Code still contains a lot of error handling code, ie:

if ( errno == ERROR_NO_5 ) { printMessage(ERROR_NO_5);} The error report contains only the type of error, not the

name of the offending function Two errors in rapid succession will result in the first one

being overwritten Also a problem with multithreaded functions using the same

variable

Printing an Error Message Reasonable in debugging mode only Simple Impacts reusability

Defeats the purpose of programmatic handling of error conditions

No control of program flow After error is printed, the program continues

Does not always make sense to the user Example: “System error 0xff4ff45”

Aborting the Program Good for fatal error Good for debugging purposes Undesirable loss of all volatile data

accumulated throughout program’s life time

Jumping to an error handler Non-local jump

C setjmp() and longjmp() Provides centralized error handler Do not have to worry about stack

unwinding Good approach for C programs, BAD for C+

+ programs When longjmp() leaves stack of the function, no

destructors are called for the objects allocated on the stack.

Raising a Signal Explicitly creates Software Interrupt by

calling function raise() and specifying interrupt handler

Good for hardware interface Memory fault Segmentation violation

Bad for typical error handling, signals should only be used for their intended purpose

Raising an Exception C++ has a convenient and safe

mechanism to raise exceptions when errors are detected and to transfer both control and error information to code that is competent to deal with the situation

Handles stack unwinding Calls destructors Enables a block of code to be protected Cut down on error handling code

Exceptions Exceptions are the proper way to bail

out of a situation which cannot be handled easily by a function itself, but which are not disastrous enough for the program to terminate completely.

Exceptions provide a flexible layer of flow control between the short-range return and the crude exit()

C++ exceptions When an error is encountered that

cannot be handled at the point of detection, an exception can be raised with the command:

throw e; Where ‘e’ could be an object, structure, or simple data type.Simple data types are not useful:throw 1; throw “Stack underflow”

C++ Exceptions The try block surrounds statements in which

exceptions may be generated Example:

try{

// Statements in which exceptions// may be thrown

} The throw statement should be executed

somewhere within the try-block: either directly or from within a function called directly or indirectly from the try-block

Catch The “catch” keyword immediately follows

the try-block Catch-block receives the thrown

exceptions. Example

try {read();

} catch( StackError s){

// Report syntax error in the input file, and close file}

Throwing exceptions in C++ Local objects should not be thrown, nor should pointers to

local objects be thrown However, it is possible to throw pointers or references to

dynamically generated objects, taking care that the generated object is properly deleted when the generated exception is caught

Example:

try{throw new Exception();

}catch( Exception * e) {

// process edelete e; // deallocate dynamically created object

}

Throwing references We could thrown referencestry {

throw *new Exception();}catch( Exception & e) {

// Process edelete &e;

}

Catching references / Safe This is totally safe

try {throw Exception(); }

catch(Exception & e) {//

}Output:

Exception() constructor is calledcatch block is processed~Exception() is called when reach end of catch-block

Catching exceptions / Unsafe This is not always safe depending on the nature of

Exception destructortry {

Exception e;throw e; }

catch(Exception & e) {//

}Output:

Exception() constructor is called~Exception() destructor is calledcatch block is processed~Exception() is destructor is called the second time

- Potential problem of double deallocation

Handling multiple exceptions A try block can throw different

types of exceptionstry { /* code */ }catch( StackError & s ) { // Process StackError}catch(MathError & m ) {

// Process MathError ) }

rethrow Occasionally, we want to catch any exceptions that fly

past a certain point, just to take some cleanup or protective actionstry { /* … */ }catch( … ) { /* evasive action */throw;

}The throw command without an argument in-side a catch

clause throws the current error again Errors that current function does not know how to

handle must be re-threw

Declaration of exception throwers The declaration of a function that may throw one or

more exceptions:void exceptionThrow() throw(char *, int);

This function may throw char * and int exceptions Note, declaration of exceptions is not mandatory

However, it declared, it notifies the user of a function that this function may throw certain exceptions, which need to be handled

Without a function throw list all responsibilities of providing the correct handlers is in the hands of the designer of the program

Derived exceptions Lets assume class D derives from

class B and exception of type D is thrown, then we can catch exception D using catch(B) {

/* Catch exception B and any classes deriving from it */}Note: Catch block can only specify the type

Stack unwinding The process of searching “up though

the stack” to find a handler for an exception is commonly called “stack unwinding”

All objects on the stack between the throw and catch point are properly destroyed by invoking their destructors This activity is of crucial importance, it is

guaranteed that all destructors of abandoned stack objects are invoked thus preventing memory leak

Stack unwinding

main()

a()

b()

c()

d()

e()Top of the stack

throw Exception

Catch Exception

Stack unwinds starting from function e(), d(), and c(). Destructors are called in e(), d(), and c() as stack unwinds, until exception is caught at b()

Destructors and uncaught exceptions Uncaught exceptions cause

program Abort and exit Destructors should not throw

exceptions If a destructor that is invoked while

an exception is pending itself raises an exception, the program terminates

Exception advices Don’t throw exceptions from libraries

Customers will not know that they need to catch an exception

When they catch an exception they will not know how to handle it

If you have to throw an exception, specify it in the prototype of the function This makes the user of the function aware that

he might need to catch an exception Never throw exceptions to return the result

of computation.

What exceptions should you throw? Failure of precondition Resource limitation Device failure Subsystem failure

More advice Catch those exceptions that you can

handle Catch and rethrow exceptions that you

can partially handle If possible, don’t use pointers and

handles on the stack but place then inside objects with proper destructors

Don’t throw an exception if you can continue

Recommended