Exception Handling
------------------

  - What happens when an error occurs in a program ?
    - How is the error handled?
    - Who handles it?
    - Can the program recover, or should it just die? 

- Types of errors
  - compile-time: caught by compiler
  - run-time: error that the system is not prepared or able to handle
    - cause the program to crash or report an error while running
  - logical: Give unexpected or unwanted program behavior
    - don't necessarily crash program

- Run-time errors also called exceptions

- Examples of run-time errors that can occur during program execution:
  - File I/O errors
  - Out of memory errors
  - Arithmetic overflow
  - Invalid parameters
  - Out-of-bounds array access
  - Invalid instructions

We have these types of error handling in C:

a) Error handling with assert
-----------------------------

- The assert macro aborts a program when a program assertion is not met.
   
  #include <assert.h>

  static Node *
  search(Node *root, int value)
  {
       // programmer assumes that his code calls search with non-NULL root 
  	assert(root!= NULL);
  	...
  }

- Use assert for debugging and for critical errors where program should be
  aborted.

b) Error handling by checking return values of functions
--------------------------------------------------------

- Assertions are too drastic for many errors
  - Aborting a word processer when a file can not be opened
- In many languages, certain return values of functions indicate errors
  - For example, search() will return NULL
  - Consider file I/O (in C)

  int process_file(char *file)
  {
    FILE *fp;

    if ((fp = fopen(file, "r")) == NULL) {
       perror("program"); // file could not be opened
	 return;
    }
    if (fread(buffer, size, 1, fp) < size) {
	 perror("program"); // file could not be read
    }
    // process file contents in buffer
    if (fclose(fp) < 0) {
       perror("program"); // file could not be closed properly
    }
  }
    
- Problems with above approach
  - Caller needs to check for each function error
    - If caller forgets to check, random errors can occur much later in program
    - For series of nested calls, error has to be checked at each level
  - Main logic and error logic are intertwined
  - No notion of grouping of errors
  - Handling a failed constructor
    - Constructors don't return values


In C++ we have:

c) Exception handling
---------------------
- Introduction
  - Provides an alternative to the above error-checking style
  - C++ exceptions represent errors that occur during program execution
  - Exception handlers are pieces of code outside the main logic which
    `intercept' (and take care of) exceptions.

- C++ exception mechanism uses three constructs
  - A throw statement which cause specific exceptions to be generated (thrown)
    - Takes one argument which can be of any data type
    - That argument is said to be thrown by the statement
  - A try block where an exception may be generated
  - A catch block where an exception generated within a related try block is
    handled
    - Catch statements specify the data type of exception which the block is
      intended to handle.


Example for out-of-bound array
------------------------------

Attempts to show that in some cases when you catch an error
you can fix it and continue instead of exiting the program.


#include <iostream>

class Array {
  int *A;
  int size;
public:
  Array(int n) {A = new int[n]; size = n - 1;}
  ~Array() {cout << "Destructor" << endl; delete [] A;}
  int& operator[](int index);
  void resize();
  void print() { for (int i = 0; i < size; i++) cout << A[i] << " " ;}
};

int& Array::operator[](int index) {
  
  if (index < 0 || index > size) {
    throw(index);
  }
  return A[index];
}

void Array::resize(){
  delete [] A;
  cout << "double" << endl;
  A = new int[2*size];
  size = 2*size;
}


int main() {
  Array a (10);
  int exception;
  
  int i, n = 10;
  do{
    exception = 0;
    try{
      for (i = 0; i < n+2; i++) {
	a[i] = i;
      }
    }
    catch (int i) {
      cout << "Out of bound index: " << i << endl;
      a.resize();
      exception = 1;
    }
  }
  while (exception);
  a.print();
}


- Exam-style question exercising exceptions in C++

Q: What is the Output of the following program ?

  #include <iostream>
  
  // MyException is a class. Exceptions can be a basic or user-defined data type
  class MyException
  {
    public:
    MyException(int code) { errcode = code; };
    int code() { return errcode; };
  
    private:
    int errcode;
  };
  
  class A
  {
    public:
    void One();
  };
  
  void A::One(int code)
  {
    // Throw an exception of MyException type
    // exception object is returned by copy
    throw MyException(code);
  }
  
  void callOneCatch()
  {
    A a;
    A b;  

    try {		// enclose code that may throw an exception within try
      a.One(1);
    }
    catch (MyException o) { // catch an exception of type MyException
      cout << "Caught MyException: " << o.code() << endl;
    }
  }
  


A: Output is Caught MyException: 1

d) More details about exceptions
--------------------------------

1) Stack unwinding 
   - Destructors for local objects called automatically for the entire sequence
     of functions when an exception is thrown
     - a and b destroyed automatically in example above


Q: What is the Output of the following program:

   void callOne()
   {
     A a;
   
     a.One(2);  // code doesn't catch exception
   }
   
   int main()
   {
     callOneCatch(); // no exception thrown
     try {
       callOne();  // exception thrown two function levels below
     }
     catch (MyException o) {
       cout << "Caught MyException: " << o.code() << endl;
     }
     callOne(); // uncaught exception
   }
       
A: Output:
   Caught MyException: 1
   Caught MyException: 2
   Aborted (core dumped)

2) Often try blocks have more than one statement and the catch statements can
   be chained

   try
   {
     // Program fragment which can generate multiple type of exceptions.
     stmt1;
     stmt2;
     stmt3;
   } catch (int e1) {
     // handle int exception
     // can use the value of e1
   } catch (AnotherKindOfException e2) {
     // handle exception
     // can use the value of e2.field
   } catch ( ... ) {  // default handler for exceptions
     // not caught by other catch blocks
     // no exception object available
   }
   
   - The first catch block whose argument matches the thrown exception is
     selected for execution
     - Last catch should be the default

3) Try blocks can be nested
   - See example given in Exceptions.tar.gz (trace.cc)

4) Exception classes can be inherited
   - Can be derived from the standard "exception class"
   - First put derived classes, then base classes in catch blocks
   - Errors can be grouped

5) bad_alloc exception returned by new() when using "#include <new>"
   (see the new_exception.cc file in Exceptions.tar.gz for an
    example.)

6) Problems with exceptions and pointers
   func()
   {
   	int *a = new int;
   	throw 5;
   	delete a;
   }
   - Heap memory for a is lost
   - Solution
     - Requires code duplication in C++
   
   func()
   {
   	int *a = new int;
   	try {
         throw 5;
   	} catch (exception e) {
   	  delete a;
       }
   	delete a;
   }


   Can catch and re-throw exception. Useful for dealing with
   delete.


   func()
   {
   	int *a = new int;
	
   	try {
         ...
   	} 
	catch (...) {
	delete a;
	throw; //rethrows the exception, will be caught and
	       //handled 
	}

	delete a; //this occurs if there is no error
   }


7) What happens in the case of run-time errors ocurring during the execution
of constructors ?

A constructor has no return value, so no error code can be checked upon return.
Exception throwing comes handy. If an error occurs, we throw an exception.
However, an object is not considered "constructed" until the constructor
completes execution. An exception thrown from within the constructor means
stack unwinding (as mentioned above).

However, an exception thrown from within the constructor means that the
destructor will not be triggered upon stack unwinding for the "partially 
constructed" object. Thus if the constructor had just allocated memory
dynamically before failing, this generates a memory leak.

Example (following the Array class again):

class Array {
  int *A;
  int size;
public:
  Array(int n);
  ~Array() {cout << "Destructor" << endl; delete [] A;}
  void initialize (int n);
 ...
};

This is how you should write the constructor:

Array :: Array (int n) {

  A =  new int [n];
  size = n - 1;
  try {
    initialize (n); // if this throws an error a memory leak may occur
  }

  catch (...) { //catch any errors
  delete [] A;  //cleans up the leak
  A = NULL;
  throw; //rethrow exception
 }
}