Author : Sobeit Void
Page : 1 Next >>
by Sobeit Void
Prelude
No, there aren’t any damsels to save or dragons to fight here. Our insidious foe comes in different guises on different operating systems – General Protection Fault, Illegal Operation, Segmentation Fault...
Our defense: Exceptions.
This document presents error-handling strategies using exceptions and how to write robust code. Code that works is good, and code that doesn’t should leave the system in the state it was before the program started. That is code robustness, and exceptions are designed exactly for such purposes.
Though C++ exceptions are platform independent, the discussion here tends towards the Windows platform.
If you feel a need to contact me, mail me at robin@cyberversion.com
Why bother with Exceptions?
Take a look at this code fragment
int main()
{
Object *p = new Object(); // assume Object is a defined class
p->Some_Method();
delete p;
return 1;
}
At first glance, there is nothing wrong with the code. After all, most C++ books have code like that. However, the above code is not robust. What if the Some_Method() function generates an error that would crash the program? Then delete p would never be called and the memory would not be freed. This is a common situation known as a memory leak.
In C++, there isn’t a garbage collector like other languages, and most people would say that is a serious flaw in C++. I used to think like that too, until I learned about exceptions.
What are Exceptions?
I will briefly cover what exceptions are here. I will not delve too deeply here since any C++ book will cover the syntax better than I can. This is meant to be a refresher course. Please consult a book for syntax details.
Exceptions are designed for error handling. There are two kinds of exceptions – hardware or software. The hardware exception occurs when you do a divide by zero, access invalid pointers, and so on. Dealing with hardware exceptions is usually platform dependant, and the C++ standard has no rules on how hardware exceptions are to be dealt with. This is where our discussion will be Windows dependant.
Example
int x = 3, y = 0;
int z = x / y; // this will cause a hardware divide by 0 exception
Hardware exceptions normally occur because of program bugs. We cannot really do much with them except handling them so we will leave this aside and focus on software exceptions first.
Try/catch/throw
Software exceptions are generated by the programmer using the keywords try, catch and throw. The try keyword is to guard a section of code against an exception; the catch keyword is to handle the exception; the throw keyword is to generate an exception.
Example
// any exception generated here will result in an illegal operation
// because it is not guarded by a try keyword
try
{
// all the code here is guarded against exceptions
throw 100; // generate an exception of type int
// Program flow will go to the catch (int) handler. If we throw another type of exception
// that we do not catch, say 'throw "String" ', then we would enter the catch(...) handler
// code here will not execute because an exception occurred above
}
catch( int )
{
// handle an exception of type int specifically
}
catch(...)
{
// handles all other kinds of exception we do not explicitly catch
}
// code resumes normally here
We can throw any type of exception – an int, a string, a class object. I generate an int exception here for simplicity. Later, we will generate a class object exception to identify the type of error.
The rule is we should catch whatever we throw.
How to try/catch
Therefore, the try/catch section is normally placed in the main section. This will ensure the program will never crash (well almost). When a function encounters an error situation that should end the program, it throws an exception.
Example
// Just some function, note we do not need a try/catch in the function.
void Function()
{
if ( 1 != 0 ) // fake some error situation
throw 100;
// this will never execute
}
int main()
{
try
{
// do some other code
Function();
// this will never execute
}
catch(...)
{
// display some error to the user
return 0; // return failure code to system
}
return l; // success
}
Note that there is only one try/catch in the main function. Functions in the program do not need try/catch blocks. You should note also that generating an exception would stop the program flow in the caller. So if a function calls a function that generates an exception, the code below the calling function would not execute.
Example
// A function that calls another function that generates an exception.
// The caller of this function would also have its program flow disrupted.
void First_Function()
{
Second_Function(); // if an exception is generated in the function, the
// exception would be propagated up to the caller
// of First_Function, until it reaches a try/catch.
// code here will not execute
}
This implies that exceptions should only be used to for serious errors and that the program should quit, hence the name Exceptions. Throwing an exception requires a catch handler so if the error is common, then a return code would be more appropriate.
The rule is to only throw exceptions for critical errors and limit your try/catch to the main program function only. Exceptions are errors but errors are not necessarily exceptions.
Rethrow
Sometimes you would like to catch the exception but still propagate the exception upwards. This is known as re-throwing the exception and is done with the throw keyword in a catch handler.
Example
void Function()
{
try
{
}
catch(...)
{
// catch the error
throw; // throw the exception upwards
}
}
This technique is normally used when the function needs to know about a specific type of exception but is not ready to handle the exception yet. I will show you how to use such a method later but you should avoid this technique if possible.
That should be about all you need to know about exception syntax. All of these should be readily available in a good C++ book.
Exception Safety and Error Handling
Scenario 1 – Exception Safety in Functions
This is where the fun part begins. What is exception safety? Well, it’s code that is exception safe. Sort of a stupid answer, I know, so let’s look at an example.
Example
void Function()
{
Object *p = new Object(); // assume Object defined
p->Some_Method();
delete p;
}
The above code is not exception safe because a memory leak may occur if an exception occurred between the memory allocation and de-allocation. Let’s take a look at another example.
Example
void Function()
{
Object p; // assume Object defined
p.Some_Method();
}
The above code is exception safe because the destructor for p is always called regardless on the program flow. Stack objects are always de-allocated automatically when they go out of scope, so when an exception occurs, p will be cleaned up when the function exits.
Pointers are evil and should be replaced with stack objects.
However, if you keep following the rule above, you will soon run of the precious stack space. What if we do really need to keep the object on the heap using new? Let’s also look at error handling strategies cause they coexist together.
Evil Goto
This is a classic way of error handling in C. It relies on functions to return a true/false for indicating success or failure.
I modified the example to include error-handling tactics as well. The below function is a bit more useful in the real world.
Example
int Function()
{
int bSuccess = 0; // flag for success, false initially.
Object *p = new Object(); // assume Object class defined
if (! p->Some_Method() ) // return encounter an error
goto Error:
// do some other code
bSuccess = true; // we finished without an error
:Error
delete p;
return bSuccess;
}
I bet you seen code like that before. Each function will return a value
Page : 1 Next >>