Because some people have no idea how exceptions should be used, I'll give you my best example of a good-to-use-exceptions-in situation here:
try{ ASerializeReader ser(filename, SE_LEVEL_FILE_ID); ASharedPtr<SEPlayField> pfnew(new SEPlayField); pfnew->Serialize(&ser); // if we havn't thrown an error by now, we have loaded successfully pf = pfnew; // set to new playfield}catch(ASerializeError e){ // give the user an error ("failed to load level")}
The above it the code used for loading a level. Note the use of a smart pointer that will clear the new play field in the event that it goes out of scope without increasing its reference count by assigning itself to the "active" playfield (pf).
Already, by using exceptions and a smart pointer, it's very safe and very easy. There are no memory leaks ever, and we can report the error and maintain control over our application.
Now the reason that this
requires exception handling is that the serialization functions (that actually do the data flattening/unflattening) look something like this:
void Example::Serialize(ASerializer* s){ s->Data(&foo); bar->Serialize(s);}
Now, because we're reading a file, a huge number of things could go wrong. The call to Data(), for instance, could fail if it falls off the end of the file (file is short, perhaps due to bad download). We could also throw for other reasons - bad version number, bad checksums, missing classes, etc. And of course the child object, bar, could also have an error.
Now, if I had to do that without exceptions, the code for that simple serialization function would look something like the following. And remember this is a very simplified function - these things could fail in a mirrad of ways, in every single place.
bool Example::Serialize(ASerializer* s){ if(!s->Data(&foo)) return false; if(!bar->Serialize(s)) return false; return true;}
As you can see, it's already way more code than we started with. However you may note that I catch ASerializeError, which gives me information about what actually failed (which may be useful to the user - particularly mod authors). The non-exception code would then have to look something like this:
ASerializeError Example::Serialize(ASerializer* s){ ASerializeError e = ASerializeError::NoError(); e = s->Data(&foo); if(e->IsError()) return e; e = bar->Serialize(s); if(e->IsError()) return e; return e;}
Notice we're introducing a lot of code repetition. At least now we're giving off an error message that is actually useful to the user (like "your file is too small", "your file is corrupt", "your file is too old").
Now this is the "10-fold increase in code" case that I mentioned in my first post. Although this example isn't a 10-fold increase, the actual usage would be. Notice that the only code that we have actually
added in that increase is just checking if there is an error - which is 100% repeated code.
And of course, if something fails to check that error condition. Then you get a fatal error that will bring down the program with a crash.
The good thing about exceptions is that different types allow you to deal with and give off different errors in different areas. For example, you might want a "out of memory" error to just cause the whole app to terminate, and perhaps give the user a message box to that effect (there isn't a lot that can be done without memory anyway). Whereas, you'd want file ("I can't load this") exception to both come from a different place (whatever is directly handling the file), and be handled in a different place (an error for the user, failture to load the level,
not an application termination).
Of course, it's easy for me, as I'm the only programmer. As some of you say - in a team, or using a library - you have to deal with the exception safety of other peoples code. Frankly that's a load of crap. The code should be
well documented so everyone knows what exceptions could be sent and recieved from where and when and why. If your programmers don't know how to make exception-happy code, then they are not qualified C++ programmers.
Even then - there are things that don't even need to care about exceptions - especially as the exceptions (unlike error codes) can pass right through them without any change in code.
Anyway, my example
is an extreme case - where there is no practical way of doing things without exceptions. The same idea applies in many other cases too. And while I would avoid using exceptions for "normal" logic flow (things that will happen regularly) especially in "realtime" code, they're very useful for handling exceptional cases in many things - reading and writing files, transmitting data over sockets, users unplugging joysticks, etc, etc. It dosn't have to be an error case, either - generally they should be used for "this particular line of program logic dosn't work, try another" - which includes more than just errors.
I really don't want to see code where the only try/catch block is this:
int main() { try {entire_program();} catch(...) {print("death");} }
Also, many people are saying how exceptions lose stack information. Lies. MSVC 7.1 allows you to automatically break-on-throw in its debugger. If your debugger can't - that is not a fault of C++.
The only limitation of is that you lose stack information for release code. This is what you see when there is an error in the Unreal Engine (I don't have an example handy). This isn't usually necessary for most programs, the reason the Unreal Engine really "needs" this functionality, is because of UnrealScript. Most other programs can get away with a well written throw-type with plenty of verbose information that can be given to the user. My ASerializeError class, for example, has a function AddSerializerInfo(const ASerializer& s) that adds information about what file was being read, what version, etc, etc - all the info a user would need (user includes mod author).
And Unreal manages to implement the stack-listing functionality using some macros anyway.
Anyway, I hope this was informative. Use exceptions responsibly people [smile].