Activity 17-1 - Exception Handling Basics



Most programmers assume that nothing unusual or incorrect will happen when they start writing their programs.  For example, when we work with a list or a file, we may assume that it is not empty.  Such assumptions reduce the amount of coding at the beginning and one can add segments to handle things that may go wrong once the core program is finished.   In C++, we often write our programs as if nothing unusual happens and then use exception-handling facilities to add code for those unusual cases.  Exception handling is commonly used to handle error situations, but perhaps it would be better to design it to handle "exceptional situations".

Dealing with an exceptional situation may mean different things.  For example, when division by zero might happen in a program, one can simply terminate the program but another programmer may decide to do something else.  In C++, exception handling proceeds as follows:
        1) Either some library software, or your code, provides a mechanism that signals the unusual situations.  This is called throwing an exception or
        2) At another place in your program you place the code that deals with the exceptional case.  This is called handling the exception.

In order to describe this better, let's look at an example.  The following program computes the Donuts-Per-Glasses of milk (dpg) by assuming that we always have some milk, i.e. amount of milk is never 0.

// P17_1.cpp - This program computes donuts per glasses of milk
#include <iostream>
using namespace std;

int main()
{
    int donuts, milk;
    double dpg;
    cout << "Enter number of donuts:\n";
    cin >> donuts;
    cout << "Enter number of glasses of milk:\n";
    cin >> milk;

    dpg = donuts/double(milk);
    cout << donuts << " donuts.\n"
           << milk << " glasses of milk.\n"
           << "You have " << dpg
           << " donuts for each glass of milk.\n";

    cout << "End of program.\n";
    return 0;
}

As you may notice, in this program at line:

        dpg = donuts/double(milk);

we may have an exceptional situation where there is no milk left or the number of glasses of milk is negative.  We know that division-by-zero is not defined and that a negative amount of milk is meaningless.  To handle this exceptional situation we can add something to this program.  So the new program may look like this:

// P17_1a.cpp - This program computes donuts per glasses of milk
// Handling special case without Exception Handling
#include <iostream>
using namespace std;

int main()
{
    int donuts, milk;
    double dpg;
    cout << "Enter number of donuts:\n";
    cin >> donuts;
    cout << "Enter number of glasses of milk:\n";
    cin >> milk;

    if (milk <= 0)
    {
        cout << donuts << " donuts, and No Milk!\n"
             << "Go buy some milk.\n";
    }
    else
    {
        dpg = donuts/double(milk);
        cout << donuts << " donuts.\n"
             << milk << " glasses of milk.\n"
             << "You have " << dpg
             << " donuts for each glass of milk.\n";
    }

    cout << "End of program.\n";
    return 0;
}

So, in the new program the if (milk <= 0) statement will prevent us from dividing by zero or from having an unreasonable number of glasses of milk. Thus, it will handle the exceptional situation for us.  We could use the C++ standard exception handling procedure to handle this situation.  Here is the program with exception handling.

// P17_1b.cpp - This program computes donuts per glasses of milk
// This program uses the C++ exception handling
#include <iostream>
using namespace std;

int main()
{
    int donuts, milk;
    double dpg;

    try
    {
        cout << "Enter number of donuts:\n";
        cin >> donuts;
        cout << "Enter number of glasses of milk:\n";
        cin >> milk;

        if (milk <= 0)
               throw donuts;

        dpg = donuts/double(milk);
        cout << donuts << " donuts.\n"
             << milk << " glasses of milk.\n"
             << "You have " << dpg
             << " donuts for each glass of milk.\n";
    }
    catch(int e)
    {
        cout << e << " donuts, and No Milk!\n"
             << "Go buy some milk.\n";
    }

    cout << "End of program.\n";
    return 0;
}

In this program we have used the basic way of handling exceptions in C++ which consists of try-throw-catch threesome.  The syntax for a basic try-block is:

try
{
    some_code
}

This block contains the code for the basic algorithm that tells the computer what to do when everything goes smoothly.  Sometimes, inside the try-block we may throw an exception.  In that case, the syntax will look like this:

try
{
    Code_To_Try
    Possibly_Throw_An_Exeption
    More_Code
}

We have an example of this case in the above program, throw donuts.  The thrown value, donuts, is sometimes called an exception and the execution of a thrown-statement is called throwing an exception.  The thrown value can be of any type.  Now imagine that when your program executes the statement, throw donuts, it throws a negative or 0 value to the catcher and the program halts.  The catch-block will receive the thrown value as e in the catch(int e) statement.  The catch-block also contains some code and is often referred to as an exception handler.

What is "e" for? The "e" in the above statement is called the catch-block parameter.  Note that the catch-block is not a function.  The catch-block parameter does two things:

    1) it is preceeded by a type name that specifies what kind of thrown value the catch-block can catch
    2) it gives you a name for the thrown value that is caught, so you can write code in the catch-block that uses the thrown value that is caught.

Exercise 17.1
Write a program that asks users to enter a number from the keyboard and computes its square root.  You need to use an exception handler to handle the case where the number is negative. Call your program ex17_1.cpp.