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 [email="robin@cyberversion.com"]robin@cyberversion.com[/email]
[size="5"]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;
}
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.
[size="5"]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 dependent, and the C++ standard has no rules on how hardware exceptions are to be dealt with. This is where our discussion will be Windows dependent.
Example
int x = 3, y = 0;
int z = x / y; // this will cause a hardware divide by 0 exception
[size="3"]Try/catch/throw
Software exceptions are generated by the programmer using the keywords [font="Courier New"][color="#000080"]try[/color][/font], [font="Courier New"][color="#000080"]catch[/color][/font] and [font="Courier New"][color="#000080"]throw[/color][/font]. 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
The rule is we should catch whatever we throw.
[size="3"]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
}
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
}
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.
[size="3"]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
}
}
That should be about all you need to know about exception syntax. All of these should be readily available in a good C++ book.
[size="5"]Exception Safety and Error Handling
[size="3"]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;
}
Example
void Function()
{
Object p; // assume Object defined
p.Some_Method();
}
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;
}
The above technique for error handling is bad because it requires the caller to check the return value of every call. The caller might not do that. You need to adopt the X-files philosophy - Trust no one. Besides, the function will become very messy when you have a lot more function calls.
Exceptions - A better solution?
Let's assume we do not use return values for error handling. Instead, we always throw an exception if we encounter an error. So the above example becomes
Example
void Function() // notice we do not need return values
{
Object *p = new Object();
p->Some_Method(); // Some_Method() can return void
if ( 1 != 0 ) // some fake error
throw 100; // if we encounter an error, always throw an
// exception like this
delete p;
}
Of course the above code is not exception safe, so let's rewrite it.
Example
void Function() // note there is no return value
{
Object *p = NULL;
try
{
p = new Object();
p->Some_Method(); // will throw an exception if error
delete p;
}
catch(...)
{
delete p;
throw; // re-throw the exception to indicate error to caller
}
}
SideTrack - SEH
I go into a small sidetrack here. VC++ has a set of keywords __try,__finally,__except known as Structured Exception Handling (SEH). It's basically error handling using exceptions for C programs.
The main limitation of SEH is that you are limited to using types that do not have destructors i.e. basic types and structs. However, you can mix functions that use SEH or C++ exceptions in your program.
Note that SEH is not portable but a VC++ specific implementation. Other compilers may support SEH but it is not guaranteed.
Example
void Function()
{
Object_Struct *p = NULL; // cannot have a destructor
__try // note the double underscore
{
p = new Object_Struct();
p->Some_Method();
}
__finally
{
delete p; // always executed when function exit
}
}
Just thought it's a nice trick to know when working with C code only. I will not go into further details on SEH. The MSDN docs have a nice section on SEH if you need to know more.
The Ultimate Solution - Smart Pointer
Let's recap where we are just in case you are lost. We want to use exceptions for error reporting instead of using return values. We also want to able to create heap objects but still have exception safety.
I said earlier about evil pointers and stack objects are always exception safe. This is the solution for our problem: a stack based pointer, commonly known as a smart pointer.
The main ideas behind smart pointers are to wrap the heap object in another stack based object (the smart pointer). The stack wrapper object is exception safe because it will be de-allocated when the function exits. Since it wraps the heap object, the heap object is cleaned up as well (in the smart pointer destructor).
The implementation of a smart pointer is actually quite advanced and requires the use of templates but all we need to know is that the destructor of the smart pointer will call delete on the heap pointer.
Let's look at it in action to understand more about it. I use a smart pointer implementation from the STL (Standard Template Library) known as the auto_ptr. Of course, you can always roll your own smart pointer classes.
Example
#include // include auto_ptr definition
void Function()
{
std::auto_ptr p( new Object() );
p->Some_Method();
}
That's it. The above code is exception safe and p is allocated on the heap. Contrast the elegance of this code with all the previous solutions.Let's look at the smart pointer in more detail. Other than the ugly looking namespace syntax, notice that we declared a stack object of class auto_ptr. We then tell the smart pointer class the type of pointer is it meant to wrap.
std::auto_ptr
We then declare an instance of the smart pointer using a default constructor and use the smart pointer as if it was the original pointer. Isn't that neat??This looks like a good time for some real code, so let's see the smart pointer in action. Take a look at the sample (SmartTest.zip) before continuing.
[size="2"]Smart Pointer Problems
Wait a minute. Aren't smart pointers the ultimate solutions? Yes, but there is a very big issue you must be aware of.
A smart pointer class calls delete on it's wrapped pointer, not delete[], so you cannot use it for arrays.
std::auto_ptr p( new Object[100] ); // BAD, NO ARRAYS
Try to do the above and you are shooting yourself in the foot.What happens if you need an exception safe array? Use the vector class.
std::vector v(100);
v[0] = 1; // use like an array
v[2] = 2;
What if you really need to use a pointer? This is mostly the case when you are working with legacy C code. Then, I'm sorry, but you have to resort to one of the messier solutions to ensure exception safety. After that, learn to really hate old C code.If you need to know more about the STL and the auto_ptr, vector class etc. and how it can save you, go get a book. I recommend The C++ Standard Library by Nicolai M. Josuttis. No, I'm not getting paid for this, though I sure wish I were.
[size="3"]Scenario 2 - Exception Safety in classes & Resource wrapping
Now that we know that we should not be using raw pointers but always use smart pointers instead, let's look at how it should be done in classes
Example
class TestClass // A classic class
{
// sniped
private:
Object* p1; // assume Object class defined
Object* p2;
};
Using raw pointers for classes is not exception safe if your class uses more than one pointer because in the constructor, the allocation between the two pointers may fail.Example
TestClass::TestClass() // Classic constructor
{
p1 = new Object();
// if some exception occurs here, then p1 is never freed and a memory leak occurs again
p2 = new Object();
}
The solution? Smart pointers to the rescue.Example
class TestClass // exception safe class
{
public:
// Constructor
TestClass() : p1( new Object() ) // use initization list
{
// delay load the smart pointer
p2 = std::auto_ptr( new Object() );
}
private:
std::auto_ptr p1; // smart pointer
std::auto_ptr p2;
};
Now you think you are smart enough to take on the world, but let me tell you there is another problem with the above code.The problem lies with the smart pointer.
Remember the problem of shallow and deep copy in classes? If you have raw pointers, you need to define the copy constructor and assignment operator or only the pointers will be copied due to the default implementation of copy constructor and assignment operator doing bitwise copying.
We have a similar problem with the auto_ptr.
Ownership of the resource
The auto_ptr specifies that there can be only one owner on the resource it wraps at any one time. If you do an assignment, the ownership is transferred and delete will not be called on the original auto_ptr.
Example
std::auto_ptr p( new Object() );
std::auto_ptr q;
q = p; // p give ownership of resource to q
// when both go out of scope, only q destructor calls delete.
What this means is with respect to copying, the auto_ptr does not cut it. If you create more than one object from the class, you cannot perform any copying operations between the objects.Frankly, the auto_ptr is a very weak implementation of a smart pointer. Designing a safe smart pointer class has given the C++ Standard committee tons of headaches. They probably ran out of time to design an all around useful smart pointer class (they have deadlines too), so we are stuck with auto_ptr.
What's the solution then? Well, you can either write your own or visit www.boost.org for a set of smart pointer classes that allow copying. You can then use these smart pointers safely in classes
Resource wrapper
Let's give a more realistic example. I will use the GetDC function from the Windows platform.
If you make a call to GetDC, the MSDN documentation states that the ReleaseDC function must be called to release the device context.
Now that you are getting paranoid about exception safety, you know that a potential leak will occur if the code between GetDC and ReleaseDC throws an exception.
What we need to do is to again make sure the GetDC/ReleaseDC calls are wrapped in a stack based wrapper class. A smart pointer cannot save us now, because the smart pointer only uses new and delete.
If you code in Windows or use any library, you usually come across an initialization and release resource function. These two functions are analogous to new and delete. They need to be wrapped up in a resource wrapper class like the smart pointer to ensure exception safety.
Unfortunately, the solution is to roll your own resource wrappers or use one from MFC. Personally, I would avoid MFC.
[size="5"]Exception class
[size="3"]The Base
Up to now, I have been throwing an int exception. As I said before, you can use throw any type of exception. So let's define a more useful error reporting class.
Example
class BaseException
{
public:
BaseException( const char * const err )
{
strncpy( buf, err, 128 );
}
virtual const char * what() // get the error string
{
return buf;
}
protected:
char buf[128]; // store the error string
};
I use a char buffer for simplicity, but I recommend using the STL string class to replace all the strings in your programs.So now instead of throwing an int, we throw a BaseException object and pass a meaningful error string to the constructor. Later in the catch handler, we can retrieve the error string using the what() function.
Example
try
{
}
catch( BaseException &e ) // note we catch by reference
{
cout << e.what() << endl;
}
Getting exceptions to work in Windows is the same as in a console program. Just wrap the main message pump function with a try/catch block.[size="3"]What can we do with it?
Now we have a base exception class, we can use inheritance to create specific types of exceptions. Each derived class can correspond to a different type of error - File not found, Not enough memory, etc.
I will give two examples here - one for dealing with Windows errors and the other for handling SEH exceptions.
[size="3"]Windows exception class
I bet you have noticed Window functions use return codes to signal errors and GetLastError() to find out what the error was. Since we are coding in Windows, we have to adapt to their weak error handling implementation.
You can see the WindowsException class in the sample later on. It just translates the GetLastError code to a system message. It's pretty simple to understand so you can just look at the source.
[size="3"]SEH Exception class
SEH in VC++ encompasses hardware exceptions too, but I bet you figured that out already. In any case, Microsoft 'suggests' C++ exception handling to be used over SEH, and has kindly provided us with a means to translate SEH exceptions to C++ exceptions.
The function we are interested in is _set_se_translator. Whenever a hardware exception occurs, the registered translator function will be called. So what we do is throw a C++ exception within the translator function, like so
Example
// the translator function
void SEH_To_C++( unsigned int u, EXCEPTION_POINTERS *exp )
{
throw int; // throw an exception of type int
}
void main()
{
try
{
_set_se_translator( SEH_To_C++ ); // register the translator
// now all hardware exceptions will generate an C++ int
// exception
}
catch(...) {}
}
Note: _set_se_translator must be called for each separate thread.[size="3"]Sample
Combining all your newfound knowledge, I present a simple sample (Exception.zip) with the exception class in action. You will see how we can incorporate and extend the base exception in the Windows environment.
I don't normally use MFC, but since I'm not getting paid for this, oh well, too bad for the code bloat. Don't blame me; I'm not the one who wrote the wizard.
The main points to note are
- The main file you should look at is BaseException.h. It contains the aforementioned exception classes.
- The entire main function is enclosed in a try/catch block, just like a console program.
- I generate an exception when the mouse is moved a few times when the program is active. Of course the rest of the MFC Appwizard generated code is not exception safe, but I'm too lazy to fix it.
I know you want more, so here it is
More information
You can store the line and source file where the exception occurred by passing in two additional parameters. Use the __FILE__, __LINE__ macros.
You can also do a stackwalk inside the exception class too. Take a look at the BugSlayer column by John Robbins at MSDN on how to do this.
Inherit from STL exception class
Instead of defining a new class by itself, your base exception class can be inherited from the STL exception class. This would allow you to catch exceptions from the standard library as well as your exceptions using one catch handler.
Evil Assert
Assertions are evil. No wait, I don't mean there are not useful, I meant the default assert implementation just calls abort, and that may not be exception safe.
I recommend throwing an exception instead when an assertion failure occurs. You can do this easily by extending the base exception class.
Extending it for DirectX
Many times, I see ingenious hacks being invented for error handling in DirectX. Personally, I'm guilty of those hacks too.
So, this is the latest hack using exceptions for a full screen DirectX application.
- Extend another class that takes in a HRESULT. In that class, translate the passed result to a string using either the D3DX library or manually translating the error code. This is similar to the classes I presented.
- In the catch handler of the main function, restore the video mode before displaying the MessageBox. This will ensure the Messagebox is always visible without a need to call FlipToGDI at all (which is a slow call).
- If the DirectX function call fails, throw an exception.
Anyway, the above 'hack' is for DirectX 7.0. I haven't worked with DirectX 8 yet so I cannot guarantee if there are more 'hacks' needed.
[size="5"]Final Thoughts - Will this guy ever stop?
Yeah, yeah, soon enough. Let's go through some more details before I sign off.
[size="3"][bquote]1. Destructors cannot throw.
[bquote]Destructors are meant for cleaning up and should never throw. If you find you need to throw in a destructor, it's best to redesign the class[/bquote][size="3"]2. No setjmp/longjmp/goto allowed.
[bquote]This is probably for the better. If don't know what I'm talking about, good for you and move along.[/bquote][size="3"]3. Asynchronous Vs Synchronous exception handling.
[bquote]Under asynchronous EH (exception handling), the compiler assumes that any line can throw an exception. Under synchronous EH, the compiler only assumes exceptions occurred where the programmer specify the throw.
What this means is that synchronous EH is more efficient for functions that don't throw exceptions, since the compiler don't generate code to keep track of the unwinding of the stack
Maybe I'm paranoid, but I like to ensure that even my release programs are exception safe, so I prefer async EH to sync EH.
As far as I can tell, sync EH might not be exception safe when a hardware exception occurs. If someone knows more about this, please enlighten me.
In VC++ 5.0, async EH was the default. In VC++ 6.0, sync EH is the default.
So if you want to enable async EH, look at the /EHa compiler flag.[/bquote][size="3"]4. Performance of exceptions.
[bquote]Setting up a try/catch block does take some resources. In general, try to reduce the number of try/catch blocks. Exceptions are errors but errors are not necessarily exceptions. So if the error is a very predictable type of error, maybe a return code is more appropriate.[/bquote][size="3"]5. Threads
[bquote]Each thread requires its own try/catch block. This would mean exceptions cannot propagate between threads. What this means is that the main thread needs some way to be notified when a spawned thread encounters an exception. I don't know, maybe your main thread needs to monitor the state of all the threads continuously, and if any thread encounters an exception, the main thread tells all other threads to end and throws an exception.
I have not found of an elegant solution with respect to threading yet. If you do have some tips, please share.[/bquote][size="3"]6. Static Objects
[bquote]Since exceptions thrown require a try/catch block to handle the exception, a static object constructor should not throw. If you must throw, you probably have to set the unexpected handler in the first line of the constructor.
Again, I have not found an elegant solution to this problem.[/bquote][size="3"]7. Windows Quirks
[bquote]No doubt when coding with Windows and its old C code, you will come across many situations requiring weird hacks.
For example, in the WM_INITDIALOG message handler in a dialog wndproc, you need a try/catch block to make a call to EndDialog in case an exception occurs while creating the dialog.
Also, when you display a MessageBox in the catch handler, you cannot pass it a NULL window handle or the program will crash when exceptions are thrown from the wndproc (certain messages).
Just be on the lookout for weird stuff like this.[/bquote][/bquote]I hope I have presented some useful information about exceptions and how to write safe code. Now, please don't write any programs that crash my system.