## Chapter 19 - Exception Handling

## Chapter Goals

- To understand how exceptional conditions can arise within a program
- To understand how dealing with exceptional conditions can make your programs more reliable and robust
- To learn how to use a variety of mechanisms to handle exceptional conditions
- To understand how to use try blocks and catch handlers in your own programs

## 19.1 Handling Exceptional Situations

- Things sometimes go wrong. E.g.,
  - User input errors
  - Device errors
  - Physical limitations
  - Component (sw) failures
- Importance increases as complexity of systems increases

## 19.1 Handling Exceptional Situations (cont.)

- Some different approaches we've seen to handling exceptional conditions:
  - The `fail` predicate used by the `stream` I/O library
  - The `assert` macro
- Some errors can be detected and resolved at the point they occur
- Others are non-local; i.e., must be resolved at a higher-level

## 19.1 Handling Exceptional Situations - Example

Consider a container class (chptr. 16), and programmers P1 and P2:

- P1 develops a stack that P2 will use
- P2 is ignorant of the implementation details, needs only the interface:

  >     Stack s;
  >     s.push(3.14);
  >     s.push(1.41);
  >     s.push(2.76);
  >     while (s.size() > 0)
  >     {
  >        cout << s.top() << "\n";
  >        s.pop();
  >     }

## 19.1 Handling Exceptional Situations - Example

- Imagine that `pop` is called on an empty `Stack`
  - P2 didn't understand the i/f, or
  - P2 wrote a function that had a logic error
- P1 is unaware of the application P2 is creating
- P2 is in a better position to recover from the error
- `Stack` must report the error to the application written by P2

## 19.2 Alternative Mechanisms for Handling Exceptions

Alternative (historical) ways of handling exceptional conditions:

- Assume errors will not occur
- Print an error message
- Special return values
- External flags
- Use `assert` to halt execution
- Error Handlers (callback functions

## 19.3 Exceptions

`try-catch-throw` mechanism

- Later addition to C++
- An error is signaled by throwing an exception
- Any type can be thrown
- If not handled (caught) locally, function exits
- Does not return to caller
- Unwinds call stack, looking for an appropriate handler

## 19.3 Exceptions

Syntax 19.1: Throwing an Exception
    throw expression;
Example: |
    throw logic_error("illegal future_value parameter");
Purpose: | Abandon this function and throw a value to an exception handler.

## 19.3 Exceptions - Example

>     double future_value(double initial_balance, double p, int n)
>     {
>        if (p < 0 || n < 0)
>        {
>           logic_error description("illegal future_value parameter");
>           throw description;
>        }
>        return initial_balance * pow(1 + p / 100, n);
>     }

Note: `logic_error` is a standard exception class, declared in `<stdexcept>`

## 19.3.1 Catching Exceptions

- Supply a handler with the `try` statement:

  >     try
  >     {
  >        code
  >     }
  >     catch (logic_error& e)
  >     {
  >        handler
  >     }

- If an error is thrown in the `try` clause, execution goes to the `catch` clause
- If no appropriate handler is found, the next outer `try` block is examined

## 19.3.1 Catching Exceptions - Syntax

Syntax 19.2 : `try` Block
    try
    {
       statements
    }
    catch (type_name1 variable_name1)
    {
       statements
    }
    catch (type_name2 variable_name2)
    {
       statements
    }
    ...
    catch (type_namen variable_namen)
    {
    	statements
    }

## 19.3.1 Catching Exceptions - Syntax (cont.)

Syntax 19.2 (cont.)
Example: |
    try
    {
       List staff = read_list();
       process_list(staff);
    }
    catch (logic_error& e)
    {
       cout << "Processing error " << e.what() << "\n";
    }
Purpose: | Provide one or more handlers for types of exceptions that may be thrown when executing a block of statements.

## 19.3.1 `try` Block - Example

- Place a handler into the main function that:
  - tells the user that something has gone wrong, and
  - offers a chance to try again with different inputs:
- Handler inspects the object thrown
- `catch` clause resembles a function, w/out a return
- `catch` calls the `what` member of `logic_error`
  - `what` returns the string passed to `e`'s c'tor

## 19.3.1 `try` Block - Example

>     int main()
>     {
>        bool more = true;
>        while (more)
>        {
>           try
>           {
>              code
>           }
>           catch (logic_error& e)
>           {
>              cout << "A logic error has occurred: "
>                 << e.what() << "\n" << "Retry? (y/n)";
>              string input;
>              getline(cin, input);
>              if (input == "n") more = false;
>           }
>        }
>     }

## 19.3.2 Values Thrown and Caught

- Can throw (and catch) any type, including integer
- User-defined objects are commonly thrown
- Implicit conversion (e.g., `int` to `double`) not performed on thrown values

## 19.3.2 Values Thrown and Caught (cont.)

This doesn't work as intended:

>     try
>     {
>        . . .
>        throw "Stack Underflow";
>        . . .
>     }
>     catch (string err)
>     {
>        cerr << err << "\n";
>     }

## 19.3.2 (cont.) Throwing Objects

Users often define their own exceptions:

>     class MyApplicationError
>     {
>     public:
>        MyApplicationError(const string& r);
>        string& what() const;
>     private:
>        string reason;
>     };

>     MyApplicationError::MyApplicationError(const string& r)
>        : reason(r) {}

>     string& what() const
>     {
>        return reason;
>     }

## 19.3.2 (cont.) Throwing Objects

Errors are now indicated by throwing an instance of this class:

>     try
>     {
>        . . .
>        throw MyApplicationError("illegal value");
>        . . .
>     }
>     catch (MyApplicationError& e)
>     {
>        cerr << "Caught exception " << e.what() << "\n";
>     }

## 19.3.2 (cont.) Objects as Exceptions

- We usually catch references:
  - Efficiency
  - Avoid slicing objects of inherited classes
- Use inheritance for exceptions:
  - Reuse
  - Standard interface
  - Create categories, so handlers can be broad or specific

## 19.3.2 (cont.) Standard exception hierarchy (partial), in `<stdexcept>`

## 19.3.2 (cont.) Inheriting From Standard Exceptions

- Use library exceptions as-is (see initial example), or
- Inherit from these to create further specialized categories:

  >     class FutureValueError : public logic_error
  >     {
  >     public:
  >        FutureValueError(string reason);
  >     };

  >     FutureValueError::FutureValueError(string reason)
  >        : logic_error(reason) {}

  - No added attributes or functionality
  - C'tor passes its argument to its parent class' c'tor
- All subclasses of `exception` have a `what()` method

## 19.3.2 (cont.) Exceptions & Inheritance

- Can still be caught with:

  > `catch (logic_error& e)`

- Or, can be caught with:

  > `catch (FutureValueError& e)`

## 19.3.2 Exceptions & Inheritance (cont.)

- Or, do both:

  >     try
  >     {
  >        code
  >     }
  >        // only catches FutureValueError
  >     catch (FutureValueError& e)
  >     {  handler1  }
  >        // catches all other logic_error
  >     catch (logic_error& e)
  >     {  handler2  }
  >     catch (bad_alloc& e)
  >     {  handler3  }

## 19.3.2 (cont.) `catch` Clauses

- Examined top to bottom
- Executes the first handler that matches, then stops processing that exception
- Match derived class before its base class
- If uncaught, the previous try block is examined
- If call stack is unwound, `std::terminate` is called
- May specify your own by using `std::set_terminate`

## 19.3.2 (cont.) Example - `catch` Clauses

Consider the function `process_record`, which calls `read`, which in turn calls `future_value`, which throws an exception. Who catches it?

>     void process_record()
>     {
>        try
>        {
>           read();
>        }
>        catch (logic_error& e)
>        {
>           cout << "caught logic_error " << e.what() << "\n";
>        }
>     }

## 19.3.2 (cont.) Example - `catch` Clauses

>     void read()
>     {
>        try
>        {
>           . . .
>           double d = future_value();
>        }
>        catch (bad_alloc& e)
>        {
>           cout << "caught bad_alloc error " << e.what() << "\n";
>        }
>     }

>     double future_value(...)
>     {
>        . . .
>        throw FutureValueError("illegal future_value parameter");
>     }

## 19.3.2 (cont.) `catch(...)`, rethrow

- `catch(...)` used to catch any exception
- Should be last in list
- Exception unnamed
- Use `throw` w/no arguments to rethrow error

## 19.3.2 `catch(...)`, rethrow (cont.)

>     try
>     {
>        code
>     }
>     catch (FutureValueError& e)
>     {
>        statements1
>     }
>     catch (...) // Catch any remaining exceptions
>     {
>        statements2
>        throw; // Rethrow the error
>     }

(See 19.3.3 for example)

## Common Error 19.2

Throwing Objects versus Throwing Pointers

- Compare the following:

  > 1. `throw FutureValueError("illegal parameter");`
  > 2. `throw new FutureValueError("illegal parameter");`

- b. must be caught like this:

  > `catch (FutureValueError* e) ...`

  (or a ptr to a parent class)
- Who deletes the object?

## 19.3.3 Stack Unwinding

Example: reading input

- `Product::read` attempts to read a single `Product` from input
- Returns `false` on the expected end of input
- Throws an exception if an error has occurred:

## 19.3.3 Stack Unwinding (cont.)

>     bool Product::read(fstream& fs)
>     {
>        getline(fs, name);
>        if (name == "") return false; // End of file
>        fs >> price >> score;
>        if (fs.fail())
>           throw runtime_error("Error while reading product");
>        string remainder;
>        getline(fs, remainder);
>        return true;
>     }

## 19.3.3 Stack Unwinding (cont.)

- C++ unwinds call stack in search of a try block to handle the exception
- Before each function is terminated, destructors are called on all stack variables
- First-class types don't have destructors:

  >     Product* p = new Product();
  >     if (p->read())
  >     {
  >        . . .
  >     }
  >     delete p; // Never executes if read throws an exception

## 19.3.3 (cont.) Exceptions and Cleaning Up

1. Make sure all stack values are instances of a class, or
2. Catch the error, clean up, and rethrow the error:

   >     Product* p = NULL;
   >     try
   >     {
   >        p = new Product();
   >        if (p->read())
   >        { . . . }
   >        delete p;
   >     }
   >     catch (...)
   >     {
   >        delete p;
   >        throw;
   >     }

## 19.3.4 Exceptions and Constructors / Destructors

- C'tors and d'tors do not return a value
- Throwing an exception is a clean way to indicate failure
  - Caveat: Static values are initialized before `main` is entered. There is no try block to catch exceptions
- If a c'tor fails, the object is not created
  - The d'tor isn't called
  - Subtle source of leaks
  (See next slide)
- Two concurrent exceptions will halt the program
  - Don't throw exceptions from within a d'tor

## 19.3.4 (cont.) Memory Leak in a Constructor

>     class DataArray
>     {
>     public:
>        DataArray(int s);
>        ~DataArray();
>        void init(int s);
>     private:
>        int* data;
>     };

## 19.3.4 Memory Leak in a Constructor (cont.)

>     DataArray::DataArray(int s)
>     {
>        data = new int[s];
>        init(s); // What happens if init throws exception?
>     }

>     DataArray::~DataArray()
>     {
>        delete[] data;
>     }

>     void DataArray::init(int s) throw (overflow_error)
>     { . . . }

## 19.3.4 Memory Leak in a Constructor (cont.)

Here's one solution:

>     DataArray::DataArray(int s)
>     {
>        data = new int[s];
>        try
>        {
>           init(s);
>        }
>        catch (...) // Catch any exception init throws
>        {
>           delete[] data;
>           data = NULL;
>           throw; // Rethrow exception
>        }
>     }

## 19.3.5 Exception Specifications

- Uncaught exceptions can be dangerous (Ariane rocket)
- Exceptions thrown by a function or method can be specified:
- An empty list denotes that no exceptions are thrown:

  > `void process_products(fstream& fs) throw ()`

- A function w/no `throw` specification can throw any exception

## 19.3.5 Exception Specifications (cont.)

Syntax 19.3 : Exception Specification
    return_type function_name(parameters)
    	 throw (type_name1, type_name2, ..., type_namen)
    }
Example: |
    void process_products(fstream& fs)
       throw (UnexpectedEndOfFile, bad_alloc)
Purpose: | List the types of all exceptions that a function can throw.

## 19.3.5 Exception Specifications (cont.)

Caveats:

- Compiler does not enforce them
  - If a function indirectly throws an exception not in its specification, the program is halted
- Specifications are not tied to function signatures during inheritance
  - An override in a derived class can throw an exception not specified in the base class definition

## 19.4 Case Study: Matrices

- Define 2 new classes:
  - `MatrixMismatchException`, inherits from the standard class `invalid_argument`
  - `MatrixIndexExceptions`, inherits from `out_of_range`
    - C'tor also takes the offending index value
    - Uses a private method to append index to string before calling the base-class c'tor
- The test program has an example of using a single catch clause to catch both of these exceptions

## 19.4 Case Study: Matrices (`matrix3.h`)

## 19.4 Case Study: Matrices (`matrix3.cpp`)

## 19.4 Case Study: Matrices (`matrixtest3.cpp`)

## Chapter Summary

1. Programs should be robust
2. Select appropriate exception handling mechanism for the task
3. Exception management becomes more important on multiple-developer projects
4. `throw` an exception to signal an error. One of the callers must supply a `try` block w/an appropriate `catch` clause