Sign in to follow this  

is Exception Handling widely used in commercial games programming?

This topic is 4584 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

we know that Exception Handling is a very important feature of C++ but i've read some free & open source games on the net hardly any Exception Handling code used in those games so i was just wondering if it is necessary to learn Exception Handling and i want to know is Exception Handling widely used in commercial games programming?

Share this post


Link to post
Share on other sites
Exception handling is, good, bad, and ugly. It greatly simplifies and reduces your source code if your app is based on classes. It's not extremely beneficial if your app isn't. But there's always a downside. For functions that can throw exceptions, the compiler has to add implicit catch blocks EVERYWHERE. This leads to massive code bloat (and the things I've tested it on weren't even as complex as commercial projects).

EDIT: Oh yeah, and there's another major downside: exceptions are SLOW. I did some benchmarking a while back, and clocked almost 0.1 ms to get from the throw to the catch. Do NOT throw exceptions for errors that are moderately common, or you can kiss any kind of decent performance out the window. Exceptions are meant to be just that: exceptional cases that almost never happen.

Share this post


Link to post
Share on other sites
Exceptions are an important tool to have (weather you use them or not) for complicated software engineering projects.

In my coding, I have found instances where exceptions are just about the only way of doing things (without a 10-fold increase in code size).

As was said before - don't do what Java does and use exceptions for absolutly everything, and very much avoid it for "normal" program flow. They are not exactly fast. However it's probably better to use exceptions in most "error" situations than to have a whole bunch of conditional jumps (for "catching" bad return codes and cleaning up).

When you don't have to check return codes and provide explicit cleanup, you'll have less overall code, and less repeditive code, which is a very good thing.


Basically, if you chose to never use exceptions, then you are chosing a posibility that your software will be poorly engineered and bug-prone and less extensible and so on (all these things that come with poor engineering).


Just make sure you know how to use them properly, and you're aware of the way they clean up stack objects and not heap objects (things that are new'd and delete'd), etc. If you know how to use them properly, you'll use them in the right situations and have better, faster programs.


The reason they arn't used in games often - probably because the developers are "silly", or they don't think of writing the game as an excercise in software engineering (and thus end up with a less flexible, buggier, less reusable program).

Share this post


Link to post
Share on other sites
This is actually a current topic on the Software Engineering - Gamedev mailing list. See http://lists.midnightryder.com/pipermail/sweng-gamedev-midnightryder.com/ for the archive. The thread started May 12.

The general consensus is the answer is no, with one specific exception. (pun intended).

Good exception-safe code is not easy. That's a big reason to avoid it up front.

Exception handling generally introduces overhead (either time and memory at runtime, or space in the application), which can be a problem when you're counting CPU cycles and every byte of memory.

In consoles, there is no OS to fall back to. You handle everything yourself. If something fails, you should provide a reasonable alternative, even if the alternative is an ugly solution. That means if a disk read fails, your code should retry it a few times, then find some way of showing an error message, rather than just throwing and causing the app to crash. The message should give the user the chance to remove the disc, clean it, and put it back. Every error should be handled locally, and return an error value if there was a problem.

On the desktop, basically the same thing is true. This is entertainment, not a productivity software. If you can't load an asset, just pick some reasonable alternative and log the error. For a texture or model, have a generic "THIS IS A PROBLEM" asset that can be used, such as a big orange texture or a model of the word "ASSET NOT FOUND", sounds could be an annoying audio message, and so on. In the release assets, replace them with non-annoying elements.

The general answer is "Do something reasonable, record it in a log file, and return an error."

Pretty much the only place you should use exceptions are situations where you are escaping from a fairly large logic block. The code should be written by somebody who understands exception-safe code, and it should be carefully reviewed. No exceptions should propogate back to the caller from a library or engine function call, since you can't count on other programmers writing exception-safe code.

Hope that helps.

frob

Share this post


Link to post
Share on other sites
I think it's becoming more common on the PC, I'm pretty sure games such as Unreal & Dungeon Siege have used them. On consoles I would not be surprised if exceptions are typically disabled.

There are serious problems with current exception models that make them tremendous amounts of work to get right, yet they fail to provide adequate debugging information and there's no integrated recovery mechanism. You lose the stack so you can't inspect variable states (C++ won't even build a function call trace automatically, and it's hard to automate it without using the __FUNC__ C99 feature). You also have to swallow an exception and turn it into an error code in order to support a retry.

It's hard to avoid exceptions in standard C++, you can only give-up and not bother trying to make the program exception safe. You can disable exceptions with some (most?) compilers, but then you can't use much of the standard library (and new can still return 0).

Share this post


Link to post
Share on other sites
The unreal engine from epic games use exceptions. If you look at the old code you can see a lot of members written in this way:

#define guard(func) {static const TCHAR __FUNC_NAME__[]=TEXT(#func); try{
#define unguard }catch(TCHAR*Err){throw Err;}catch(...){appUnwindf(TEXT("%s"),__FUNC_NAME__); throw;}

void foo::bar()
{
guard(foo::bar)
//some code
unguard
}

it is used also for logging and debugging cause unguard is #defined in other way if the debug flag is on. They make something like gLog->log(blah). This method is very good for me and I use it as well. This method is used by unreal developer for adding safety and clarity cause if the engine raise an error exception you can see from the crash dialog the stack unwinding, something like: Engine::WinMain::MessageBump::Core::Blah. It's professional and very good for error reporting. If I remember well I've read on Stroustrup book that the simple try catch must not add any kind of code overhead so you can use it without warning (notice the "must not"). If catching an exception is very slow this doesn't matter cause, as the name say, an exception is an exceptional event. When one is raised you can try to manage it and restore the internal data of the program, if not you must only gracefully end all.

Share this post


Link to post
Share on other sites
Quote:
Original post by frob
This is actually a current topic on the Software Engineering - Gamedev mailing list. See http://lists.midnightryder.com/pipermail/sweng-gamedev-midnightryder.com/ for the archive. The thread started May 12.


Holy crap. I'm reading that and I'm thinking:

"What the hell - are people thinking that exceptions are only used for this..."

int main()
{
try
{
the_whole_program();
}
catch(...)
{
print("something bad happened");
}
}


There are places other than "critical failure" that exceptions are good to use!

Heck - if that is all people are using exceptions for - why not just call abort().

Share this post


Link to post
Share on other sites
Don't use exceptions for things that are likely to happen sometime, like a missing texture. At least not in production code.

But I would advise to do use them as much as possible in Debug builds. After all, even a missing texture should be fixed when encountered. Let the game testers work with the Debug build for a long while. Try, as good as possible, to ensure you always have a fallback for Release mode. For the example of texture loading, return null and deal with that gracefully in the rest of the code. Test that!

You might want to use something like the code below in Release builds:

#define try
#define throw {log(__FUNC_NAME__);} if(false)
#define catch if(false)


So the general idea is that you should use exceptions as a better alternative to asserts. They're use in Debug builds and meant for code testing.

Share this post


Link to post
Share on other sites
Quote:
Original post by C0D1F1ED
Don't use exceptions for things that are likely to happen sometime, [...]

So the general idea is that you should use exceptions as a better alternative to asserts. They're use in Debug builds and meant for code testing.


No.

The one and only point of exceptions is, as their name implies, to identify and handle exceptional situations. If you throw an exception, you imply that:

- What happened is normal and expected program behavior under that situation (and that the exception-throwing behavior is fully documented)

- The situation can be restored to a stable state (and the exception catching mechanism will help you do it)

An assertion failure meets neither of these two conditions: it is meant to signal unexpected behavior (situations that should not occur), and usually an assertion is not recoverable (if only because to recover, you'd need to actually know why the unexpected behaviour occurs). Using exceptions only as an assertion mechanism is a gross misuse.

Quote:

For the example of texture loading, return null and deal with that gracefully in the rest of the code.


This is an interestingly inefficient thing to do. Notice that most of the code you write to "deal gracefully" with an error will consist in:

- Detecting an error has occured in a given function you just called
- Determining the error, and if you can handle it right there
- Handling it if possible
- Propagating it by returning immediately an error code

Which is also casually referred to as "exceptions". Only you do it yourself.

This set of operations, when written by hand, is often underoptimized (for instance, it will require a call to GetLastError( ) because there's only so much data you can get from a NULL return value) and slower than exceptions (unless, of course, you claim to spend time optimizing your error recovery code, at which point I'll just roll my eyes and go away).

Not only that, but it is harder to maintain since you actually have to propagate the error by hand (on the contrary, an exception propagates itself automatically until it is caught). When having to do this on a scale of hundreds of possible error-generating calls, it is usually enough to drive someone crazy (or, more often, to end up in code that does not handle exceptional situations well). It is also less readable, since the error-checking code is to be found around the function calls (as opposed to catch blocks that occur at the end of the entire try block) and as a consequence the code for normal situations and exceptional (sic) situations is mixed together, making the program logic slightly harder to follow even when commented. Besides, it requires a hired programmer to be trained in your own error-handling system (whereas any decent programmer knows how try { } catch { } blocks work).

Exceptions are a much more elegant, fast, reliable, maintainable, universal and easy way to implement "graceful dealing" with errors on a scale larger than 100-line programs, and when available they should be used, as they are an option superior to any other complete method of handling exceptional situations, in every possible way.

Of course, if you go the incomplete error handling way (such as giving up when an error happens instead of trying to recover) or only need error checking in a 100-line function (not realistic, but still, who knows?) then exceptions are not really useful.

Share this post


Link to post
Share on other sites
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].

Share this post


Link to post
Share on other sites
'Fraid I don't have all that much to add here, having not much in the way of experience with large projects (I just hit 4000 lines of code... That is to say, nothing..) or extreme error handling (Sounds like it could be a sport, doesn't it? Extreme Error Handling? [smile]). But I'll give my two cents anyway..

Basically, I would (well, plan to, I haven't implemented this yet) use exceptions whenever problems out of my control occur, such as a disk read error, network error, whatever, as well as in places where the error can not simply be shrugged off/fixed immmediately, such as if a texture could not be found.. Let's take that example. I would throw an exception with a point to the texture object that was supposed to be filled, and in the catch block, display an all-purpose MessageBox (not a Windows one, but a specialized object) and fill the texture object with the default 'error texture'.

Maybe this could be done by simply putting all that code in the catch block where the error occurs.. However, let's imagine I then have a number of different texture-loading related methods, such as LoadAlphaMap and LoadNormalMap, or even LoadDisplacementMap. Now what? Do I paste the code where the error can occur in all those functions as well? This leads to code bloat, as well as unmaintainability. Am I then supposed to remember to update all four sets of error handling code when I want to change it? This can be handled much better with exceptions, I would think.

Oh ya, one question.. A lot of people have been mentioning the Unreal source code, could someone perhaps point out to me how I could get my hands on this, as I am most interested in it [smile]

Share this post


Link to post
Share on other sites
Andrew Russell, I don't know where in the hell you learned to program, but you should consider revising your code instead of immediately calling it 'bloated'.


//your 'bloated' code
bool Example::Serialize(ASerializer* s)
{
if(!s->Data(&foo)) return false;
if(!bar->Serialize(s)) return false;
return true;
}

//the same code, written *better*
bool Example::Serialize(ASerializer* s){
return (s->Data(&foo) && bar->Serialize(s));
}



Share this post


Link to post
Share on other sites
Ah - but - two things:

First: that was an over-simplified version, the "actual" use is far more complicated. It was layed out like that for simplicity. It can also contain jumps (if, while, etc) which rules out using a single statement for it at all.

Second: It fits in well when you add in the ASerializeError error type in the next example.


I am aware of your reduction in number of lines. The first time I wrote the example code using ASerializeError it was more succinct, but it was harder to interpret. Particularly because it uses the fact that ASerializeError has a conversion operator to bool. It looked something like:

ASerializeError Example::Serialize(ASerializer* s)
{
ASerializeError e;
if(e = s->Data(&foo)) return e;
if(e = bar->Serialize(s)) return e;
return e;
}

That still lets me do advanced flow-control things. If I wanted to do it your way, it would have looked something like:


ASerializeError Example::Serialize(ASerializer* s)
{
ASerializeError e;
if(e = s->Data(&foo) && e = bar->Serialize(s)) return e;
return e;
}

// or even:
ASerializeError Example::Serialize(ASerializer* s)
{
ASerializeError e;
if(e = s->Data(&foo))
e = bar->Serialize(s);
return e;
}

The entire idea of the code in the first place, both in my examples and in my actual production code, is to be more readable and eaiser to use and modify.

Not to make it take up less lines, and possibly make it a few percent faster if the compiler misses an optimisation. This is loading code anyway - it hardly needs to be realtime.


Not to mention - I know what I'm doing - I don't use any of those methods - I use exceptions for the most readable, succinct and efficient code possible.


In conclusion - calling me out for my "bad" code was totally unnecessary. I hope, in your eagerness to do so, you havn't missed the point.

Share this post


Link to post
Share on other sites
I haven't missed the point, but when you don't use exceptions (like I do), you tend to try to cut the code down to the simplest form, while keeping it working (exception prevention, instead of exception handling). Exceptions have their place, but you don't *need* to use them.

Share this post


Link to post
Share on other sites
Well, the entire point of my post is where you do need exceptions, at least to write sensible code. The examples I provided were not entirely sensible (which was the idea). Neither was your code - and it was much, much less readable (and would not have worked in the "real" case either).

The general case of my specific example is any set of large, recursive calls that operate on tree structures, where the leaf calls of that tree can generate... well... exceptions, which in turn cause the entire tree to be invalidated.

Without exceptions, this would require huge ammounts of return-value checking. Not to mention, having to manually delete objects that are created on the heap.

Hence you missed the point.

Share this post


Link to post
Share on other sites
Quote:
Original post by Gorax
I haven't missed the point, but when you don't use exceptions (like I do), you tend to try to cut the code down to the simplest form, while keeping it working (exception prevention, instead of exception handling).

I find this extremely hard to believe and it is contrary to my experience with code I have written or used, regardless of scale or complexity.
Quote:
Exceptions have their place, but you don't *need* to use them.

Of course you don't need to use them. You don't need to use a medium or high level language. You could write everything in machine code or on a Turing machine. The point is that exceptions make it easier to write simple, correct, grokable code. Use them.

Enigma

(I think I just used up my quota of <b> tags for the day [lol])

Share this post


Link to post
Share on other sites
Quote:
Original post by Gorax
I haven't missed the point, but when you don't use exceptions (like I do), you tend to try to cut the code down to the simplest form, while keeping it working (exception prevention, instead of exception handling).


surely by cutting the code to the simplest you've lost readability? and until something is judged to be a bottleneck readability > speed considerations.

I find the charge of 'exceptions being slow' an intresting on, generally when an exception accures you've fallen off the happy fast path and have crashed into a bumpy field of errors and problems which have to be dealt with, at which point I'd consider speed the least of my problems.
That said, if you use exceptions for every "error" condition you deserve any runtime pain you recieve [grin]

Share this post


Link to post
Share on other sites
Quote:
Original post by C0D1F1ED
Don't use exceptions for things that are likely to happen sometime, like a missing texture. At least not in production code.

But I would advise to do use them as much as possible in Debug builds. After all, even a missing texture should be fixed when encountered. Let the game testers work with the Debug build for a long while. Try, as good as possible, to ensure you always have a fallback for Release mode. For the example of texture loading, return null and deal with that gracefully in the rest of the code. Test that!

You might want to use something like the code below in Release builds:
*** Source Snippet Removed ***
So the general idea is that you should use exceptions as a better alternative to asserts. They're use in Debug builds and meant for code testing.


good example i apply a texture called missing texture which is hardcoded so i can t be missing :)


and i really suggest to use perror

and if you try to find some errors here and there without using all these exception handling features just pipe the result of stderror to a file

run <yourapplication> 2> error.txt

Share this post


Link to post
Share on other sites
Quote:
Original post by _the_phantom_
That said, if you use exceptions for every "error" condition you deserve any runtime pain you recieve [grin]


That may be a very good guide to where you can skimp on exceptions. If something (be that a single function, or a bunch of things) can just return a very simple "yes"/"no" type error, then exception handling probably isn't necessary.

However if you've got several layers of functions in the stack, or you've got complicated error types, or your resource aquisition can fail - leaving otherwise-dead objects, or it's a non-simple error condition, then it's probably a good time to use exceptions.

For example, my file interface class returns true/false for various functions. My serializer that uses that file class will throw an error when it receives that true/false, though. Adding exceptions to the file class would be unnecessary bloat. A try/throw/catch is more complex than the if/else/return method, and hence, is only warrented in more complex situations.

Of course, denying the existance of those more complex situations is rediculious.

Share this post


Link to post
Share on other sites
http://www.joelonsoftware.com/items/2003/10/13.html

This guy has a well thought out reason for why not to use exceptions.

When it comes to errors, there are no free rides.

Share this post


Link to post
Share on other sites
Quote:
Original post by Gorax
Andrew Russell, I don't know where in the hell you learned to program, but you should consider revising your code instead of immediately calling it 'bloated'.


if( !foo() ) return false;
if( !bar() ) return false;
return true;

//the same code, written *better*
return ( foo() && bar() );


Paraphrased the code a little bit here, but... correct me if am wrong, but isn't that actually *different* code? The original version will bail out as soon as something goes wrong with foo() while the 'better' version will try to execute bar() no matter if foo() succeeds or not..?

Share this post


Link to post
Share on other sites

This topic is 4584 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this