Exceptions - Part 1

(Nine days to better error handling)

Error Handling

Recall that given a quadratic equation of the form:
\(ax^2 + bx + c = 0\)
We can solve the equation for its roots with this formula:

Day One

For simplicity, we will only calculate one root (the "+" portion of the equation).

The QRoot function (for Quadratic Root) looks like:

double QRoot(double a, double b, double c)
{
  double determinant = (b * b) - (4 * a * c);
  return (-b + std::sqrt(determinant)) / (2 * a);
}
We would use it something like this:
#include <iostream>
#include <cmath>

double QRoot(double a, double b, double c)
{
  double determinant = (b * b) - (4 * a * c);
  return (-b + std::sqrt(determinant)) / (2 * a);
}

int main()
{
    // -0.438447
  std::cout << "QRoot a=1, b=5, c=2: " << QRoot(1, 5, 2) << std::endl;

    // Error, taking square root of negative number (-nan)
  std::cout << "QRoot a=1, b=2, c=5: " << QRoot(1, 2, 5) << std::endl;

    // Error, divide by 0 (-nan)
  std::cout << "QRoot a=0, b=2, c=5: " << QRoot(0, 2, 5) << std::endl;
  
  return 0;
}
The output
QRoot a=1, b=5, c=2: -0.438447
QRoot a=1, b=2, c=5: -nan
QRoot a=0, b=2, c=5: -nan
What are the pros and cons of this approach?

Day Two

double QRoot(double a, double b, double c)
{
  double determinant = (b * b) - (4 * a * c);

  if (determinant < 0) // protected against: std::sqrt(-x)
  {
    std::cout << "Can't take square root of a negative number." << std::endl;
    std::abort();
  }
  else if (a == 0)     // protected against: x / 0
  {
    std::cout << "Division by 0." << std::endl;
    std::abort();
  }

  return (-b + std::sqrt(determinant)) / (2 * a);
}
Note: The client code is unchanged. Output from the abort() function (depends on the version of the compiler.)

GNU (on Cygwin):

Can't take square root of a negative number.
    160 [sig] a 1716 open_stackdumpfile: Dumping stack trace to a.exe.stackdump
    160 [sig] a 1716 open_stackdumpfile: Dumping stack trace to a.exe.stackdump
1572257 [sig] a 1716 E:\Data\Courses\Notes\CS170\Code\Exceptions\a.exe: *** fatal error - 
E:\Data\Courses\Notes\CS170\Code\Exceptions\a.exe: *** called with threadlist_ix -1
Microsoft:
Can't take square root of a negative number.

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information. 
Borland:
Can't take square root of a negative number.

Abnormal program termination
What are the pros and cons of this approach?

Day Three

bool QRoot(double a, double b, double c, double *result) // Could use references as well
{
  double determinant = (b * b) - (4 * a * c);

    // protected against: std::sqrt(-x), x / 0
  if ( (determinant < 0) || (a == 0) ) 
  {
    *result = 0.0;  // return something safe
    return false; // indicates there was a problem
  }

  *result = (-b + std::sqrt(determinant)) / (2 * a);
  return true;  // indicates all is well
}

int main()
{
  double answer;
  double success = QRoot(1, 5, 2, &answer);
  if (success)
    std::cout << "QRoot a=1, b=5, c=2: " << answer << std::endl;
  else
    std::cout << "QRoot failed for some reason" << std::endl;
    
  return 0;
}
What are the pros and cons of this approach?

Day Four (read all about exceptions)

These examples use simple built-in data types for the exception types. You will likely never do that. This is to keep the examples very simple while focusing on the "exceptional" part of the discussion. Proper use of exception classes will be shown near the end.

A format of the try..catch mechanism:
int main()
{
  . . .
   
  try
  {
    // code that might cause an exception (throw) and needs
    // to be protected
  }
  catch (???)  // which type of exception to catch?
  {
    // code that will handle the exception (catch) from
    // the try block above
  }

  . . .
  
}
You can catch multiple exceptions thrown from a try block:
int main()
{
  . . .
  
  try
  {
    // code that might cause an exception (throw) and needs
    // to be protected
  }
  catch (const char *p) // catch a const char pointer
  {
    // code that will handle the char pointer exception from
    // the try block above
  }
  catch (int i) // catch an integer
  {
    // code that will handle the integer exception from
    // the try block above
  }
  catch (std::exception e) // catch an "exception" object
  {
    // code that will handle the "exception" object from
    // the try block above
  }
  
  . . .
  
}
Notes:

Day Five (Use exceptions)

The client and non-client code has been modified now:
double QRoot(double a, double b, double c)
{
  double determinant = (b * b) - (4 * a * c);

    // protected against std::sqrt(-x) and division by 0
  if (determinant < 0) 
    throw("Can't take square root of a negative number.");
  else if (a == 0)     
    throw("Division by 0.");

    // We only reach this point if no exception was thrown
  return (-b + std::sqrt(determinant)) / (2 * a);
}

int main()
{
  try  // protect code
  {
    std::cout << "QRoot a=0, b=5, c=2: " << QRoot(0, 5, 2) << std::endl;
  }
  catch (const char *message)  // catch a const char pointer exception
  {
    std::cout << message << std::endl;
  }
  
  return 0;
}

Output:
Division by 0.

Day Six

Exception specifications have been deprecated in C++11 so you won't use them. They have been replaced with the noexcept specifier, which we won't cover here. I've just left these notes here for historical reasons.

double QRoot(double a, double b, double c) throw(const char *, double)
{
  double determinant = (b * b) - (4 * a * c);

    // protected against std::sqrt(-x) and division by 0
  if (determinant < 0) 
    throw(determinant);       // throw double
  else if (a == 0)     
    throw("Division by 0.");  // throw const char *

    // We only reach this point if no exception was thrown
  return (-b + std::sqrt(determinant)) / (2 * a);
}

int main()
{
  try  // protect code
  {
    std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
  }
  catch (const char *message)  // catch a const char pointer exception
  {
    std::cout << message << std::endl;
  }
  catch (double value)  // catch a double exception
  {
    std::cout << value << std::endl;
  }
  
  return 0;
}

Output:
-8
To indicate that a function doesn't throw any exceptions, use empty parentheses:
double SomeFun(double a, double b, double c) throw()
{
  ...
}
What does the following output?
int main()
{
    // protect code
  try 
  { 
      // Determinant will be negative
    std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
  }
  catch (const char *message) // catch a const char pointer exception
  {  
    std::cout << message << std::endl;
  }
  catch (double value) // catch a double exception
  {   
    std::cout << value << std::endl;
  }

    // protect code
  try 
  { 
      // a is 0 (divide by 0)
    std::cout << "QRoot a=0, b=2, c=1: " << QRoot(0, 2, 1) << std::endl;
  }
  catch (const char *message) // catch a const char pointer exception
  {  
    std::cout << message << std::endl;
  }
  catch (double value) // catch a double exception
  {   
    std::cout << value << std::endl;
  }
  
  return 0;
}
What does the following output?
int main()
{
    // protect code
  try { 
    std::cout << "QRoot a=3, b=2, c=1: " << QRoot(3, 2, 1) << std::endl;
    std::cout << "QRoot a=0, b=2, c=1: " << QRoot(0, 2, 1) << std::endl;
  }
  catch (const char *message) // catch a const char pointer exception
  {  
    std::cout << message << std::endl;
  }
  catch (double value) // catch a double exception
  {   
    std::cout << value << std::endl;
  }
  
  return 0;
}

Day Seven

Unwinding the Stack after an Exception

Assuming the same code for the QRoot function, what will this code do

  1. assuming QRoot does not throw an exception?
  2. assuming QRoot throws an exception? (const char *)
void f1()
{
  std::cout << "Starting f1..." << std::endl;
  QRoot(...);  // program flow depends on this call
  std::cout << "Ending f1..." << std::endl;
}

void f2()
{
  std::cout << "Starting f2..." << std::endl;
  f1();
  std::cout << "Ending f2..." << std::endl;
}

int main()
{
    // protect code
  try { 
    f2();
  }
  catch (const char *message) // catch a const char pointer exception
  {  
    std::cout << message << std::endl;
  }
  catch (double value) // catch a double exception
  {   
    std::cout << value << std::endl;
  }
  
  return 0;
}
Assuming this call in f1: (no exception is thrown)
QRoot(1, 5, 3);  
We get
Starting f2...
Starting f1...
Ending f1...
Ending f2...

Assuming this call in f1: (division by zero)
QRoot(0, 5, 3);  
We get
Starting f2...
Starting f1...
Division by 0.

Re-throwing Exceptions


void f1()
{
  try 
  {
    QRoot(0, 5, 3);  // division by 0
  }
  catch (const char *s) 
  {
    throw("Error! Please call 1-800-DIV-ZERO for help");
  }
}

int main()
{
    // protect code
  try 
  { 
    f1();
  }
  catch (const char *message) // catch a const char pointer exception
  {  
    std::cout << message << std::endl;
  }
  
  return 0;
}

Output:
Error! Please call 1-800-DIV-ZERO for help
Remember this rule:

NEVER catch an exception that you do not intend to do anything about. If you don't know what to do with the exception that you catch, DO NOT CATCH IT! It is meant for some other part of the program to handle.

Day Eight (Exception Classes)

class SomeClass
{
  // code here
};

void f1()
{
  throw SomeClass(); // construct and throw SomeClass object
}

int main()
{
  try 
  { 
    f1();
  }
  catch (const SomeClass &s) 
  {  
    std::cout << "Caught an exception of type SomeClass" << std::endl;
  }
  
  return 0;
}

Modifying our String Class

Recall the String class we developed:
class String
{
  private:
    char *string_;     // the "real" string

  public:

      // Constructors, destructor, etc...

      // overloaded [] operators for subscripting
    char & operator[](int index);
    const char & operator[](int index) const;
};
Our original "error handling":
char & String::operator[](int index)
{
  int len = strlen(string_);     // Get length of internal string
  if (index < 0 || index >= len) // Make sure the index is valid
  {
    cerr << "Bad index" << std::endl; // If bad, print message
    abort();                          //   terminate program
  }
  else
    return string_[index]; // Return the char at index
}
Adding an Exception Class

  1. Create a class that will be used to "announce" subscript exceptions.
  2. Write code to throw the exception, if necessary. (String class)
  3. Wrap the potentially "unsafe" code in a try block. (Client code)
  4. Include a catch block in the client to handle the exception. (Client code)
class SubscriptError
{
  public:
    SubscriptError(int Subscript) : subscript_(Subscript) {};
    int GetSubscript() const { return subscript_; }

  private:
    int subscript_;
};
Implementation:
char& String::operator[](int index) 
{
  int len = strlen(string_);      // Get length of internal string
  if (index < 0 || index >= len)  // Make sure the index is valid
    throw SubscriptError(index); // Throw exception if invalid

  return string_[index];         // Return the char at index
}
We would code the client like this:

int main()
{
  String s("Hello"); // Create string "Hello"
  try 
  {
    std::cout << s[0] << std::endl; // Get the first character and print it
    s[9] = 'C';                     // Attempt to change tenth character
    std::cout << s << std::endl;     
  }
  catch (const SubscriptError &se) 
  {
    std::cout << "Bad subscript: " << se.GetSubscript() << std::endl;
  }
  
  return 0;
}

Output:
H
Bad subscript: 9

Handling Memory Allocation Failure

Now that we know how to deal with out-of-memory errors in C++, we can deal with them.

List::Node *List::new_node(int data) const
{
    // Make sure we have room
  Node *node;

  try
  {
    node = new Node(data); // create the node
  }
  catch (const std::bad_alloc& ex)
  {
    std::cout << "New failed" << std::endl;
    std::cout << ex.what() << std::endl;
    std::abort(); // Maybe do something better?
  }

  return node;
}