C++ Exception Handling by (08 August 2000) |
Return to The Archives |
Introduction
|
Exception handling can be a very personal and complex topic. The C language gave the programmer very few exception handling capabilities, as the programmers wanted more control over exceptions themselves. Thankfully, the C++ standards committee crafted a simple, but powerful form of exception handling for the C++ language that still gives the programmer quite a bit of control. Many coders eschew this method that I will present shortly, so more power to you. Most of the information I learned on C++ exception handling was from the wonderful Deep C++ column on MSDN. |
Dark Days of C
|
A typical 'C' function may look something like this:
Quite messy if you ask me. You are extremely dependent on the return values of functions, and if an error occurs in the program, such as a header value not being correct, you have to constantly have code to handle this. If you allocate, say, 10 pointers in a function, I bet half of that function's code will be dedicated entirely to exception handling. Then there's the fact that if DoSomething returns an error code, the calling function will have to take appropriate steps to correctly handle the error, which, pardon my language, can be a huge pain in the ass. |
Try-catch-throw
|
I'll show you the C++ version of the above function later on. I'm not going to
just drop it in your lap and expect you to know everything (even if you do...).
I will build up to it, starting with an explanation of try-catch-throw.
Now for an example that should hopefully make their purpose evident:
If you were to run this snippet of code, the output would be: Caught exception number: 1 Now, comment-out the throw 1; line, and the output will be: No exception detected! Okay, this seems extremely simple, but exception handling can be very powerful if used correctly. catch can catch any data type, and throw can throw any data type. So, throw dumbclass(); works, as does catch(dumbclass &d) { }. catch can catch any type, but it does not necessarily have to specify a variable. This is perfectly valid:
As is:
Also, catch can catch every type of exception if need be:
The ellipses signify that all thrown exceptions are caught. You cannot specify a variable name for this method, though. I also recommend that if you catch'ing anything but a basic type (long, short, etc.), you catch them by reference, because, if you do not, the whole thrown variable has to be copied onto the stack, instead of just the reference to the thrown variable. If multiple variable types can be thrown, and you want to get the actual variables, not just an indicator that this type was thrown, you can do a multiple catch:
|
Thrown Exceptions
|
When an exception is thrown, the program keeps going up the function stack
until it encounters an appropriate catch. If there is no catch statement in
the program, STL will handle the thrown exception via the terminate handler,
but it usually does so less gracefully than you could, by popping-up a nasty
dialog box or any variety of other things, usually by calling abort. The absolute most important characteristic about this is that while it is stepping-up in the function stack, the program is calling the destructors of all local classes. So, voilą, you don't have to keep including code to free memory, etc. if you use classes often, even if they are just inline'd wrapper classes. I'd like to point you to http://www.boost.org right now. They have an excellent smart pointer class -- scoped_ptr, along with several other useful classes. It is pretty much equivalent to STL's auto_ptr, but I like it better. Either one is just fine to use. |
Overloading the Global New/Delete Operators
|
I'd like to refer you to another tutorial now. The excellent "How To Find Memory
Leaks" tutorial describes a great process to detect memory leaks by
overloading the new and delete operators. The method he proposes only works for
operator new/delete, not operator new[]/delete[]. It is fairly easy to figure
these out, as they accept the same parameters. Okay, so you ask why I mention overloading these global operators in an exception handling tutorial, and an answer you shall receive. I modify Dion Picco's operator new/new[] to throw an exception if they fail in my projects. I also define non-debug versions. Source can be found for my implementation at the end of this tutorial. Now, I make my implementation of new/new[] throw my own exception class called "Exception", which is in exception.h in the source accompanying this tutorial. STL defines a class called "exception" in If you overload new, etc. like mentioned, you can do:
Of course this seems like more code than the standard "is a equal to NULL?" The thing is, it's not, because you can call several functions in a try block that allocate memory or anything else that can throw an exception. The following example uses my CFile wrapper, but you can use whatever you want, even the iostream classes. Don't worry, as classes such as vector are very optimized, so it's just a myth that these classes induce a significant overhead. Now we can get back to our infamous DoSomething function, with a function that calls it:
If you don't agree that this looks much nicer and is much easier to use than the C version, I don't know what is. =) Now we see that C++ exception handling's power lies within its ability to step out of the current function and automagically call any local class destructors along the way. If a critical error occurred in a regular C program, the coder might be forced to call exit() or abort(), but neither of these automatically calls the local class destructors on its way out of the program. In actuality, they don't even step up the function stack! |
Empty Throws
|
So, we can see now how powerful and useful this "new" method is. A try-catch
block can contain another try-catch block, and this block can throw anything, too,
which will be caught by the outer catch if there is no corresponding inner catch,
or the catch throws something. One thing I have neglected to mention is that throw
need not even have anything after it:
This could be used in extreme situations where you don't even care what was thrown. |
Application
|
A practical application of using try-catch-throw can be in a game. Say you have
a cMain class and instantiate a Main based off of it:
In your main() or WinMain() function, you can do something like:
Your cMain::Loop() function may look something like this:
|
Conclusion
|
I've shown you to the best of my abilities how to use C++ exception handling, but only you can decide if you want to use it. The methods presented in this article can help speed development time and make your life a whole lot easier. There are a plenty of topics I failed to cover in this article, and if you want to learn them, I suggest you visit Deep C++. |
|