MIRA
Error and Exception Handling

Contents

Overview

This document acts as a manual. For requirements and further implementation details see Exception (Requirements). Exception handling is a mechanism designed to handle the occurrence of exceptions, special conditions that change the normal flow of program execution.

Different error handling strategies

No error Handling

Advantages

Disadvantages

Error handling with return values

Advantages

Disadvantages

Error handling with exceptions

Advantages

Disadvantages

Using exceptions

Exceptions can be thrown using the MIRA_THROW macro. You should always use this macro instead of the throw keyword, since the macro adds additional diagnostic information about the source file name and line number where the exception was raised:

string readString(istream& stream)
{
...
if (stream.fail() )
MIRA_THROW(XIO, "Invalid format");
}

The macro takes two parameters, where the first parameter specifies the exception class. The second parameter is a human-readable string that describes the error that has happened.

To add additional information to that error string you can use the << stream operator:

MIRA_THROW(XRuntime, "Critical error in device '" << deviceName << "', OS error code: " << lasterror());

Usually, you won't need to handle each exception since they will be handled by an upper instance, e.g. the caller that calls your method. In these cases you let them simply pass through your code:

string myMethod(istream& stream)
{
...
Parser parser;
...
string name = readString(stream); // may throw exception on read error
string value = readString(stream);
parser.parse(name, value); // will throw exception on syntax error
...
}

If an exception is thrown within one of these three calls in the above example, the processing of your method will be aborted and all local objects (e.g. the parser-Object) will be destructed cleanly. In this way, the stack is unwinded upwards until a caller handles the exception. If none of the callers handles the exception the application will terminate with an error message.

To handle an exception you need to surround the code portion that might throw an exception with a try-catch block:

void readFile(const char* filename)
{
string value;
try
{
ifstream stream(filename);
value = myMethod(stream);
}
catch(XIO& ex)
{
// if we get this kind of exception we can recover by using a default
// value:
value = "Default Value";
}
...
}

Exceptions MUST be caught by-reference to avoid performance penalties and to enable all features of our exception system.

A catch-block only catches exceptions of the specified class or derived classes. To catch different types of exception you can add multiple catch-blocks. To catch all exceptions regardless of their type use catch(...):

try
{
// code that might produce an exception
...
}
catch(XRuntime& ex)
{
// catch all exception that are derived from the class XRuntime
// and do special treatment for runtime errors
...
}
catch(XLogical& ex)
{
// catch all exception that are derived from the class XLogical
// and do special treatment for logical errors
...
}
catch(std::exception& ex)
{
// catch all std::exceptions (our mira - Exceptions will be included since
// they are derived from std::exception)
}
catch(...)
{
// catch all remaining exceptions (e.g. int, string, etc.)
// and do some generic treatment
...
}

Instead of using catch-blocks to fully handle an exception you may use them to do some cleanup or to bring your program into a determined state. Afterwards you can "rethrow" the exception by using the throw; keyword:

MyClass* myObject = new MyClass;
try
{
// code that might produce an exception
...
}
catch(...)
{
delete myObject; // delete the created object to avoid memory leaks
throw; // rethrow the exception
}

Instead of using the throw keyword you can also use the MIRA_RETHROW macro. It allows you to add additional diagnostic information, that was not available to the code that produced the exception but might help the user to identify the problem:

string readString(istream& stream)
{
...
if (stream.fail() )
MIRA_THROW(XIO, "Invalid format");
// here we do not know where the stream comes from
}
string readXmlStream(istream& stream)
{
try
{
...
value = readString(stream);
}
catch(Exception& ex)
{
// here we know, that we tried to parse an XML stream and can provide
// additional information like the tag and the line number
MIRA_RETHROW(ex, "... in XML tag <" << lastTag << "> at line: " << currentLine);
}
}
void readFile(const char* filename)
{
ifstream s(filename);
try
{
readXmlStream(s);
}
catch(XIO& e)
{
// here we additionally know the file name where the error has happened
MIRA_RETHROW(e, "... in file: " << filename);
}
}

If an exception is thrown in the above example, the following error string will be generated:

Invalid format
... in XML tag <parameter> at line: 12
... in file: ~/mytest.xml

This error string will be shown to the user if the exception is never catched and the application is terminated. Additionally, this string can be obtained by calling the what() method of the std::exception (which is also implemented by mira::Exception). Beside the error string the mira::Exception also keeps track of the callstack which can be returned using the callStack() method:

try
{
...
}
catch(Exception& ex)
{
// ignore the exception, but show at least some message,
// mira::Exceptions also allow to track the callstack:
cout << "Exception: \n" << ex.what() << endl;
cout << "Stack: \n" << ex.callStack() << endl;
}
catch(std::exception& ex)
{
cout << "Exception: " << ex.what() << endl;
}

The output generated in the above example may look like this:

Exception:
Invalid format
... in XML tag <parameter> at line: 12
... in file: ~/mytest.xml
Stack:
#1  0xb6a004cf in readString(istream&)
    at /home/test/ExceptionExample.C:13
#2  0xb7fd7dcd in readXmlStream(istream&)
    at /home/test/ExceptionExample.C:24
#3  0xb6c30165 in readFile(istream&)
    at /home/test/ExceptionExample.C:37

There are already several exception classes defined in the Exceptions.h header. The type of the thrown exception should follow some criteria:

You can add your own exception types when needed.

Guidelines

General

There are several tips one should keep in mind when using exceptions:

When should I use exceptions?

An exception is by name any situation where something exceptional and unexpected happens that was not foreseen by the designer or programmer. There is a very fine line between exceptional and expected. After all, one can say: "Whenever a programmer checks for an error the error was expected somehow!" This is why there is no general guideline for "when to use exceptions". Nonetheless some hints will be given:

Exceptions can also be used to avoid ugly de-initialization statements on return.

// without exceptions
void init()
{
FILE* f = NULL;
HANDLE* driver = NULL;
char* memory = new char[20000];
DriverSession* session = NULL;
f = openHugeFile("test.bin");
if ( !f )
{
MIRA_LOG(ERROR) << "Open file failed";
delete[] memory;
return;
}
if ( !openDeviceDriver(myDevice, &driver) )
{
MIRA_LOG(ERROR) << "Open device failed";
delete[] memory;
close(f);
return;
}
if ( !initDevice(driver) )
{
MIRA_LOG(ERROR) << "Init device failed";
delete[] memory;
close(f);
closeDriver(driver);
return;
}
if ( !openSession(device, &session) )
{
MIRA_LOG(ERROR) << "Open session failed";
delete[] memory;
close(f);
closeSession(session);
closeDriver(driver);
return;
}
if ( !startSession(session) )
{
MIRA_LOG(ERROR) << "Start session failed";
delete[] memory;
close(f);
closeSession(session);
closeDriver(driver);
return;
}
}
// with exceptions
void init()
{
FILE* f = NULL;
HANDLE* driver = NULL;
char* memory = new char[20000];
try
{
f = openHugeFile("test.bin");
if ( !f )
MIRA_THROW(XMy, "Open file failed");
if ( !openDeviceDriver(myDevice, &driver) )
MIRA_THROW(XMy, "Open device failed");
if ( !initDevice(driver) )
MIRA_THROW(XMy, "Init device failed");
if ( !openSession(session) )
MIRA_THROW(XMy, "Open session failed");
if ( !startSession(session) )
MIRA_THROW(XMy, "Start session failed");
}
catch(XMy& ex)
{
MIRA_LOG(ERROR) << ex.what();
delete[] memory;
close(f);
closeDriver(driver);
closeSession(session);
return;
}
}

When should I avoid exceptions

As for the question "when should I use exceptions" there are no general guidelines for this question. But again some hints to follow:

How should I handle exceptions?

Often the best way to deal with exceptions is to not handle them at all. If you can let them pass through your code and allow destructors to handle cleanup, your code will be cleaner.