CPSC170
Fundamentals of Computer Science II

Lab 15: Exceptions

Things can go wrong when programs execute. A function could be called with inappropriate arguments, a file might not be found in the location specified in the program, or a number could be divided by 0. Situations like these can give rise to exceptions. An Exception is a mechanism for a program to indicate that something has gone wrong. Exceptions can be thrown and caught. When something goes wrong, an exception can be thrown with the keyword throw. An exception is caught with the keyword catch. Here's an example.


//PRE: n >= 0
//POST: RV = n! if n >= 0 and n! can fit into an unsigned long
//      variable, otherwise, the integer n is thrown as an exception
unsigned long factorial(int n){
  if(n < 0){
    throw n; // Illegal argument exception
  }
  
  unsigned long previous  = 1; // used to detect arithmetic overflow.
  unsigned long factorial = 1;

  for(int i = 2; i <= n; i++){
    previous = factorial;
    factorial *= i;
    if(factorial < previous){
      throw n; // overflow exception.
    }
  }
  
  return factorial;
}

The function factorial() throws an exception when its input is negative. It throws the input integer n. factorial() also detects when an arithmetic overflow occurs while computing the factorial. When an overflow is detected it throws the input integer. So, what does this mean for the factorial()? It means that any client code using factorial() can catch these exceptions. Here's a simple example.


int main(){
  int a[] = {1,2,3,4};
  int input = -8;

  try{
    std::cout << factorial(input) << std::endl;
  }
  catch(int n){
    if(n < 0){
      std::cout << "Illegal argument exception: " << n
     		 << "! is undefined" << std::endl; 
    }
    else{
      std::cout << "Overflow exception: not enough bits to compute "
		<<  n << "!" << std::endl;
    }
  } 
  return 0;
}


When calling factorial() in main we wrap it in a try-block. Following this is a catch-block. It catches an integer. Since factorial only throws integers, any exception thrown by factorial will be caught by this catch-block. Here we use the exception thrown to display the reason for the exception. Comment out try and catch and see what happens. Any type can be thrown, even classes. Alter factorial() so that it throws a character instead of an integer when the input is negative. Following the previous catch-block, add a catch-block to catch characters. Test your program to make sure it catches both integers and characters.

Exception Class

A user can define a class that has code to handle a particular exception. The following class contains the definition of an exception class, where the Exception object simply stores an error message and prints it via a public function that can be called to handle the exception.


#pragma once

#include < iostream >
using namespace std;

class Exception {
 private:
  char * msg;

 public:
  // PRE: message is defined.
  // POST: message is the error message stored in this exception object. 
  Exception (char * message) {
    msg = message;
  };

  // PRE: This object is defined and has an error message.
  // POST: The exception is handled, i.e., the error message is printed. 
  void handle() {
    cout << "Exception: " << msg << endl;
  };
  
};


The following program shows the usage of the exception mechanism of C++.

 
#include < iostream >
#include "Exception.h"

using namespace std;

// PRE: x != 0
// POST: Throws an exception if x is 0.
void f (int x) {
  if (x == 0) {
    throw (Exception((char *)"x = 0"));
  }
  cout << x << endl;
}

int main () {
  int primes[] = {2, 3, 5, 7};
  char names[][5] = {"abc", "def", "ghij", "aaa"};
  int x = 0;
  
  try {
    f(x);
    cout << "After returning from function" << endl;
  }
  catch (Exception e) {
    e.handle();
  }
  
  cout << primes[0] << " " << primes[3] << endl;
  cout << names[0] << " " << names[3] << endl;
  return (0);
}
 
 

Study the program and its output. Change the value of x to a non-zero number in the assignment statement in the function main just before the call to the function f. What do you expect? What is the output from the program? When an exception is caught (in the catch clause), one can take some action (as in the above program), or simply throw the exception to the calling function. Then, control is passed to the calling function, and the calling function needs to handle the exception. The post condition for a function that could throw an exception must state this fact, so that the client for this function, i.e., the function calling this function, can have code to catch the exception and handle it.

Fraction Class Exceptions

  1. Modify the constructor of Fraction so that if a user attempts to create a fraction with a denominator of 0, then an exception is thrown.

  2. Add a member function Fraction divide(const Fraction& frac)that divides fractions. It should throw an exception if frac is 0.

  3. Write a function, readNum, that prompts the user for a positive number, and reads in the number that the user inputs. If the user inputs a negative number or a zero, the function should throw an exception. Otherwise, the function should return the number.

    Now write a main function that uses the function readNum repeatedly, until the user enters a positive number. When the function returns a number and not an exception, your main function should output the square of the number that the user entered.