Pixelate:Issue 13/Exception Handling

From Allegro Wiki

Jump to: navigation, search
Exception Handling
Original author: Chris Barry
some mail
Website:
zip:

Greetz, and salutations. Hopefully you've all sufficently recovered from your New Year's party hangovers and are prepared for more C++ goodness. Today we'll take a quick look at how to do exception handling. This is good to know not only for the sakes of expanding our own programming knowledge, but because a lot of standard C++ stuff (like the STL, or even the new keyword) throws exceptions, and we would be wise to know how to deal with this. Now, there's a lot of info about this topic on the internet, I've found. So rather than give lots of indepth info and re-type what others have already done (not to mention violate the whole theme of "Tips and Tricks"), I've decided to just write an intro to the subject. If you think you can benefit from this, you can continue research into the topic yourself.

Exception handling is a slightly more advanced way of handling errors than just checking a function's return value. It's also the only way to handle a failed constructor, since constructor's don't have return values at all (note: throwing an exception in a destructor is a terrible idea; don't unless you really know what you're doing!) It's also got the neat catch that while a program can ignore a function's return value; it can't ignore a thrown exception (unless it enjoys crashing), which forces the programmer to make sure he plans for possible errors. The basic idea is that the program tries to do something, and if something goes wrong in the code, the program "throws" an object, which some block of code labeled "catch" receives and deals with. This involves interrupting the normal flow of the program's execution and checking for a suitable "catch block" in the current scope. If it can't find one, it pops the current function from the stack and the search resumes from the code that called it. This is known as "unwinding the stack". If no suitable catch block is encountered, eventually the stack unwinds all the way to and beyond int main() and the program terminates. But if a suitable catch block is encountered, the exception is considered "handled" and program execution moves on from there. The "catch block" can be made to handle only certian types of exceptions, or all kinds.

As already implied, this requires three keywords; try, throw, and catch. Let's toss out a small example for discussion:


void DoSomething() {
   // assume something bad happened and throw an exception
   throw string("Houston, we have a problem.");
}


try {
   DoSomething();
}
catch(string& error) {
   // something bad happened; deal with it
}

A few things of note here:

  • The "throw" keyword won't work outside a try block.
  • This particular "catch" only handles strings. If this try block threw something else, either another catch block would handle it, or the program would just crash.
  • As a rule, pass exceptions by reference, not by value.
  • In real world applications, you might have several catch blocks to deal with different kinds of exceptions.
  • You can even just throw nothing (throw;) and deal with it in a catch(...) block.

One nice thing about handling exceptions like this: let's say you've got a bunch of C-style functions that return a value (true or false) based on whether they succeeded doing whatever they had to do. Say you've got 5 or so that all need to succeed before your OpenGL window is properly initialized or something. Now you could either check all of the return values individually and deal with cleaning up half-initialized resources each time, or you can just throw an exception when a function fails, clean up once in the catch block, and be done with it. Much neater and less error prone.

I also mentioned before that exception handling is a good way to deal with failed constructors (the usual method of checking a return value fails, since constructors have no return value). Let's try a more complicated setup that demonstrates this; this uses the Allegro library since I just happen to have this example at hand, but anyone not familiar with it should still be able to follow what the exception is doing:

 // main.cpp
 #include <allegro.h>
 #include <fstream>
 #include <string>
 using namespace std;
 
 
 class CBitmap {
 	public:
 		BITMAP *bitmap;
 		CBitmap(string filename) {
 			bitmap = load_bitmap(filename.c_str(), NULL);
 			if(!bitmap) throw string(filename + " does not exist!\n");
 		}
 		~CBitmap() { destroy_bitmap(bitmap); } // no need to check for NULL
 };
 
 int main() {
 	allegro_init();
 	install_keyboard();
 	set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);
 
 	BITMAP  *buffer = create_bitmap(SCREEN_W, SCREEN_H);
 
 	try {
 		CBitmap bmp("test.bmp");
 		clear_bitmap(buffer);
 		blit(bmp.bitmap, buffer, 0, 0, 0, 0, bmp.bitmap->w, bmp.bitmap->h);
 		blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
 		readkey();
 	}
 	catch(const string& error) {
 		ofstream ofile("error.txt", ios::ate);
 		ofile << error.c_str();
 		ofile.close();
 	}
 
 	destroy_bitmap(buffer);
 
 	return 0;
 }
 END_OF_MAIN()
 

Okay, here's what happens. The bitmap class tries to load a bitmap (in a try block). If the loading succeeds, all's well and the bitmap is displayed onscreen. If not (say the file doesn't exist), the program logs an error. When we see that the bitmap isn't displayed, we check the error message and fix the problem. Notice that the object isn't actually created if the exception is thrown.

This is a good display of how exception handling is used. Even though I have been subtly equating exceptions and return values, don't start replacing all your C-style functions that return results into functions that throw all the time. Exceptions should only be thrown under exceptional circumstances; the stack unwinding can be a source of slowdown in your program, especially if you overuse it. I like to deal with failed constructors in this manner, and it comes in useful in several other situations. But failed constructors is definitely the best use for it. Experiment to see how this can help you.

You can also specify when you declare a function what exceptions it throws. The prototype for the class constructor above might be written like so:


CBitmap(string filename) throw(string);

So anyone using your class knows from the class header (or docs) that he should be prepared for this function to throw a string object should something go wrong. Likewise, much of the C++ Standard Library throws some exceptions using standard classes for just this purpose (bad_alloc(), out_of_range(), overflow_error(), etc.) Some are even derived from others, and they have special functionality to tell you what went wrong. You would do well to try and learn these so you can deal with them should the need arise, or even just to learn what exception are capable of. Remember: it's in your own best interests to deal with these errors!

That should be enough to get you started with exceptions. Try them in your own code to see how they can benefit you, and be sure to continue learning as much as you can about them. Scott Meyer's More Effective C++ and Bjarne Stroustrup's The C++ Programming Language Third Edition both have lots of information on this (and other topics, of course).

Issue 13: The Diet Issue
Previous:
Matrix Math Part Two: Projection Matrices
Present:
Exception Handling
Next:
jnrdev #1 - tilebased collision detection and response
Personal tools
Adsense