Error Handling

Aims

To enable students to:

  1. understand various error handling techniques

  2. use function return values

  3. use exceptions

Understand various error handling techniques

Bugs can be considered to be problems in a program that cause undesirable behaviour. Previous notes made suggestions on how to minimise the chances of coding bugs, such as uninitialised variables, entering a program.

A related area is the handling of error situations within a program. Errors include invalid user response, the end of an input file, failure of system calls, etc. Programs that can not handle such situations are bugged and liable to fail in unexpected ways.

Many C++ functions represent error conditions through the value they return after execution. Frequently negative values indicates an error occurred, and positive that all is well. Using this knowledge several ways of structuring code may be followed.

One approach is to finish execution of the current function whenever an error takes place, as in the following:

int iSomefunction ()
{

   ...

   ptrMem = new char;

   if (ptrMem == NULL)
   {
       return -1;
   }

   ...

}

Although simple to implement, such an approach presents several problems. In complicated functions it is often very difficult to work out all conditions in which the function will terminate, consequently future maintainers may miss certain failure conditions. Further, large functions often consume resources, such as files and heap space, during their execution. The developer of a function that terminates in multiple places must ensure all resources allocated up to that point are freed, a somewhat onerous task.

It is frequently cleaner to only have one entry and exit point for a function. In this way it is easier for a developer to ensure all appropriate resources are freed and a maintainer to find their way around the code. Unfortunately, this can make the function itself appear more convoluted, as in:

int iSomefunction ()

{

    ...

    ptrMem = new char;

    if (ptrMem == NULL)
    {
        iErrVal = -1;
    }
    else
    {

        ...

    }

    return iErrVal;

}

An alternative to the introduction of an error indicator (iErrVal in the above code) is the use nested if statements. In uncomplicated functions this can be useful, but it is very easy for your code to spread along way across the screen.

In an attempt to simplify error handling within source code C++ has introduced exception handling - note the Turbo C++ compiler does not fully support this mechanism. Exceptions remove the need for explicit checking of function return codes, instead a single block of code is placed at the end of a function and is called by the compiler if any called functions fail. A typical piece of code appears as:

int iSomefunction ()
{

    try
    {

       ...

       ptrMem = new char;

       ...

       return 0;
    }

    catch ()
    {
        return -1;
    }
}

Use function return values

When generating your own return code values, do so in a consistent way. For example, if you are using negative numbers to represent failure conditions always do this, do not use positive values in some situations.

If possible try and reserve values for specific error conditions, for example -5 may be used to represent a file open failure, and only a file open failure.

Document your return codes to assist in future maintenance. Functions should indicate what values they are likely to return to their callers.

Never totally rely on the documentation of another function. To code defensively it pays to expect functions to return other values to those specified in their documentation. It is common for code to be modified, but the corresponding documentation to be left untouched.

Use exceptions

In order to process an exception your code needs a catch block. These portions of code can be written to handle exceptions from a specific class, as in:

catch (CMyObject)
{

    ...

}

which will handle those raised by the CMyObject class, or generic in which case no class name is provided in the catch statement.

If an exception is raised within a function that has no handler, then the failure is passed down the stack of executing functions until a handler is met that can cope with it. If none is found then the process is terminated.

To raise exceptions a program uses the throw statement, which is very similar to return. For example, to throw and catch a simple error message the following code may be used:

try
{

    ...

    throw "Memory allocation failure!";
}

catch( char * str )
{
    cout << "Exception raised: " << str << 'n';
}

return 0;  

Download