Skip to main content
GameDev.net gamedev.net
🔒 Locked

When use what Error-Handling

Started by XaserIII Jul 13, 2011 at 8:50 PM 23 replies 5.5k views
Original Post
XaserIII
XaserIII
Hi!

I'm still programming my little game and just wondered how to best deal with unexpected behaviour, i.e. when something fails that should work.
Well the short term to describe this is Exception Handling, but when should I use what method?

For example, if I expect my Func1 to return "true" and I check this via an if I could either throw new std::exception(...); and have to catch it somewhere or I could return NULL for the function calling that function and just pass the problem on, what the exception also does somehow.

So I would like to have some opinions about this.

Thanks,
Xaser
SuperVGA
SuperVGA
Humm, it's not nice to pass on the problem, -how would you then (easily) find the cause of the problem? Too much exception handling, or in performance-critical parts of the code is not good, but if you're not releasing anything yet it might be good for increased bug tracability. I'd definitely not return null... If it can be avoided, and mostly it can.
XaserIII
XaserIII
Well its a general problem, for debugging as well as for the release. The release version will have to be able to handle such problems as well.

Both ways do their job and I for myself can't really tell what has more advantages in what situation, thats why I'm asking.

Xaser
SimonForsman
SimonForsman

Hi!

I'm still programming my little game and just wondered how to best deal with unexpected behaviour, i.e. when something fails that should work.
Well the short term to describe this is Exception Handling, but when should I use what method?

For example, if I expect my Func1 to return "true" and I check this via an if I could either throw new std::exception(...); and have to catch it somewhere or I could return NULL for the function calling that function and just pass the problem on, what the exception also does somehow.

So I would like to have some opinions about this.

Thanks,
Xaser


If something that shouldn't fail fails an exception is a good way to deal with it, for example in a xml model loader you could throw an exception if the file is of the wrong format (This allows you to pass far more information about the error than a simple return value would and the calling code could decide how to deal with the error far better than the model loader itself can (Should the error be logged ? do you need to know at what line in your code your failed call originated from ?, should you display it to the end user and if so, how should you display it ?, solving all this with just simple return codes can become extremely messy. (Take a look at the old mysql functions in php and its error handling if you want to see how bad things can be when you don't have exceptions (exceptions was added in php5)

if an error code can tell the calling function all it needs to know then you don't need (and shouldn't use) exceptions though.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
XaserIII
XaserIII
Well thanks, I guess exceptions are better in my case, as they can carry much more information and distinguishing between error codes etc will just fill the code and make it less readable.

Xaser
blutzeit
blutzeit
IMHO I've found it most practical to use exceptions for truly exceptional cases only. In other words, a typical execution should never throw any exception.

For example, take the following function:
int ParseInt(char* input)


What if the input is not an integer? How should this be handled, should it throw an exception or not?
The answer depends on where you put the responsibility to check whether the input is valid or not.

1) The caller is responsible for validating input (perhaps via a companion "IsInt()" method), hence invalid input is an exceptional case, and it should throw.

2) The ParseInt method takes responsibility of validating input, and is prepared to handle anything. A better signature might then be:
bool TryParseInt(char* input, int& output)
openwar  - the real-time tactical war-game platform
Hodgman
Hodgman
In C# or Java, exceptions are fine.
In C++, exceptions should never be used. Ever. At all.
rip-off
rip-off

In C++, exceptions should never be used. Ever. At all.
[/quote]
You go too far, sir!

More seriously, there isn't a problem using exceptions in C++. You're already using them (unless you disable them, or never allocate memory using new). They are the only way to write invariant-enforcing constructors that can fail.

But yes, if you can design your program such that it doesn't actually throw an exception then that is generally preferable. I'm curious to your reasoning about never though, Hodgman.


throw new std::exception(...);
[/quote]
In C++, throw by value and catch by const reference. You don't want to deal with ownership problems when handling an exception.
XaserIII
XaserIII
Well Thanks for all the opinions, even though I'd like to know on what you based this one.


In C# or Java, exceptions are fine.
In C++, exceptions should never be used. Ever. At all.


I have decided to deal with it like Blutzeit said. If the unexpected behaviour is non critical for the function or even could be expected somehow I will use ifs. I however I got the problem that a negative function call will indicate me that something critical is wrong, which will influence the whole algorithm then I think exceptions are the better choice.

Thanks,
Xaser

ADIT:
In C++, throw by value and catch by const reference. You don't want to deal with ownership problems when handling an exception. [/quote]

So just throw 5 or what? why?? what problems could occur?
rip-off
rip-off
By using "new", you dynamically allocate the exception, which is unnecessary, and just means you have to remember to catch it by pointer and delete it when done. It could also be NULL. Not nice. When you throw by value, and catch by const reference, this is handled for you.

Compare:

std::exception *make_exception()
{
// Avoid std::bad_alloc during error
return new(std::nothrow) std::exception();
}

void problem()
{
std::exception *e = make_exception();
throw e;
}

int main()
{
try
{
problem();
}
catch(const std::exception *e)
{
if(e)
{
TellTheUserSomethingWentWrong(e->what());
delete e;
}
else
{
// Something went wrong, we don't know what though.
TellTheUserSomethingWentWrong("Various problems, all malign");
}
}
}

Contrast:

void problem()
{
throw std::exception();
}

int main()
{
try
{
problem();
}
catch(const std::exception &e)
{
TellTheUserSomethingWentWrong(e.what());
}
}


Note that where C++ throws exceptions (e.g. std::bad_alloc), it throws by value, so you'd end up having a mix of catching by pointer and const reference if you try to handle these.
XaserIII
XaserIII
Ah okay yea I think I was to much in C# there.. ^^

Thanks,
Xaser
sam_hughes
sam_hughes


In C++, exceptions should never be used. Ever. At all.
I'm curious to your reasoning about never though, Hodgman.
[/quote]

I'm in the same camp. If allocation fails, abort the process -- you should replace new (and malloc) with versions that abort the process when things go wrong. int Parse(char *) is an antipattern, since you're validating and converting the values in separate steps. If your function can fail, and it's important, return a return code with a compiler attribute that requires it not be ignored. If it's not important when it fails, ignore the return code. If an assertion fails, abort the process. The reason is that it's too easy to write C++ code that leaks or has undefined behavior in the presence of exceptions. For example when you write some templated code and casually assume that the operator=(const T&) won't throw an exception. This happens all the time. If you want to deal with exceptions you have to put try/catches in lots of places. Also you have to teach every new developer about how superparanoid you have to be about exceptions, if you want to actually do it correctly. We don't hire seasoned C++ experts, there are not enough people in the intersection of Smart and Seasoned C++ Expert for us to do that.

Also, exceptions are useless. If your code is a nest of asynchronous callbacks, they're useless. If allocation fails, kill the process. You can't do anything about it anyway. You don't want fine-grained error recovery information, because error-recovery logic is error-prone. You just want "kill the process," or "stop doing this." Keeping the process alive risks breaking the user's data. Once something unexpected happens the entire process is untrustworthy. Exceptions are generally of the form "Something broke, here's a string message in case you want to log it." [1] Sometimes when parsing a protocol (say, for communication to a replication slave) you'll get protocol exceptions, and then they mean "kill the slave." But since there's one kind of exception, it's just as easy to use return codes. And then you can't forget to handle certain types of exceptions, and you don't have the engineering difficulty of converting from one exception type to another.

[1] Which is an anti-pattern, you really want, "Something broke, I logged it ASAP and killed the process."

Exceptions all boil down to two extremes: "handle the problem immediately and locally, and gosh darnit now I have to remember to wrap every call to this function" or the other extreme, "stop everything, and move on." In the latter case it's perfectly fine to use a return code. Return codes, in the places where you want them, are not actually strenuous.

Exceptions are less scary in memory-safe languages and so their use is less discouraged.


Also, don't write invariant-enforcing constructors. Have a default constructor that leaves an object in an uninitialized state and then have an initialization method. If anybody tries to do anything with an uninitialized object, abort the process. Straightforward runtime failures is better than exceptions.

Edit: But sure, there are probably times and places when exceptions are soooo good and it's time to drink the kool-aid.
XaserIII
XaserIII
Hmm.. interesting^^

But still couldn't convince me fully. You are right in many points, but also wrong in some others.
For Example about the try / catch blocks in loads of places, true but it still divides perfect running code and exception handling clearly and I would say it is about the same effort than endless long if blocks checking what return code is given back etc.

Xaser
Rattrap
Rattrap

Exceptions all boil down to two extremes: "handle the problem immediately and locally, and gosh darnit now I have to remember to wrap every call to this function" or the other extreme, "stop everything, and move on." In the latter case it's perfectly fine to use a return code. Return codes, in the places where you want them, are not actually strenuous.


I find this statement funny. So basically instead of having to wrap a function call in a try/catch block, you want to wrap it in a if/else or switch block? Possible performance issues aside (to be determined by profiling), you are basically just trading one test for another (hopefully, since I frequently see examples where no test is performed).


Exceptions are less scary in memory-safe languages and so their use is less discouraged.


If you are using RAII (as you should be in C++), it can be a very memory and resource safe language. And this applies to whether your are using exceptions or return codes.
"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]
rip-off
rip-off
I appreciate the sentiments, for games in particular. But I want strong RAII and classes that enforce invariants. This generally requires me to throw exceptions, which is a pain (I wish there was another way) but I find it is worth the trade off.


The reason is that it's too easy to write C++ code that leaks or has undefined behavior in the presence of exceptions.
[/quote]
RAII helps. It can ensure you don't leak, and bring you some of the way towards weak exception safety. A stronger guarantee requires some kind of transactional semantics so that work in progress can be safely discarded if an error occurs. This is hard even with error codes. For certain tasks, RAII makes this easier. You build a temporary object with the work in progress, use an exception safe "swap" function to write it to the new location, and then any resources that need to be removed will die with the temporary object.

If you want to deal with exceptions you have to put try/catches in lots of places.
[/quote]
In my own code I rarely need to try/catch. Object construction is mainly during setup, all invariants are established and there is no user data that would be lost by throwing an exception up to main(). Once the game is running, exceptions pretty much never happen (out of memory situations aside), so it is mostly harmless.

In my usage of exceptions, they cause the program to die, but they at least try to run destructors to cleanup resources (open files and sockets are the big things I'd like to close cleanly if possible).

If anybody tries to do anything with an uninitialized object, abort the process.
[/quote]
How do you detect this?
Antheus
Antheus

In C# or Java, exceptions are fine.
In C++, exceptions should never be used. Ever. At all.


Let's throw out the standard library then as well - it uses exceptions. And boost. And just about everything else. Forget about Qt or similar toolkits.

And then one slowly starts to understand why they say that the best C++ is C.
Zoner
Zoner

[quote name='Hodgman' timestamp='1310628930' post='4835154']
In C# or Java, exceptions are fine.
In C++, exceptions should never be used. Ever. At all.


Let's throw out the standard library then as well - it uses exceptions. And boost. And just about everything else. Forget about Qt or similar toolkits.

And then one slowly starts to understand why they say that the best C++ is C.
[/quote]




I'm not a fan of C++'s exception model. A lot of libraries use it too much, and finding good C++ libraries that don't can be difficult at times. Alternative libraries at this point tend to be C libraries many of which aren't all that good (you know the kind, calling strcat and strcpy like those two functions are the best function's ever written all the while visual studio is flooding the build log with tens of thousands of potential buffer overrun and security warnings).

Even with exception C++ handling 'disabled' you can still throw, the app will still terminate in some manner, and you can at least put a chunk of stack walking code into your exception object's constructor and get a backtrace. The only real downside to the 'game engines should not have exception handling enabled' is that you can't do true a RAII design without it. If constructor's can't throw, then you have to do two-phase init instead, which starts to sound a lot like C programming The destructors on scope objects getting called when going out of scope is still a useful property though so RAII designs aren't completely broken with EH disabled.
http://www.gearboxsoftware.com/
EJH
EJH
Btw, in C++ is there any way for a try-catch block to handle any exception whatsoever? For example, in an XNA game I work on, during beta testing we put try-catch block around the main game loop. Any uncaught exception would be handled there, game shut down gracefully, and an email sent to us with the exact exception (type and line of code) and the player's Steam name so we can contact them if needed. Basically like this:


// main game update function
Game.Update()
{
try
{
// call all game update routines
}
catch(Exception ex)
{
// catch any exception and send an email
// with exception info and user's Steam name to developers
// notify user and shutdown gracefully
}
}


Is there any way to do that in C++ with a try-catch block?
rip-off
rip-off
You can use catch with ellipses:

try
{
// whatever
}
catch(...)
{
// error handling
}

I would only do this at the highest possible level, such that I avoid trying to handle things like out-of-memory errors. I don't know about sending an email to the developers with the user's steam name, I would be very unhappy with any program that tries to contact the internet without my consent, especially if they decide to send potentially sensitive information. You'd need to be very careful of running afoul of data protection laws.
the_edd
the_edd

I'm in the same camp. If allocation fails, abort the process -- you should replace new (and malloc) with versions that abort the process when things go wrong.

"should" is a poor choice of word here, IMHO. There are cases where it's probably a sensible thing to do, but due to C++ being what it is -- one of the few ways you can squeeze the last drops of performance out of a machine -- it's still used heavily for applications that do a lot of data processing.

If I was doing some video editing and the application simply gave up because memory was exhausted, I'd get my money back (or submit a patch ;)). Lots of simulation software, also falls in to this kind of situation and this is the bread and butter of C++.

Similarly, I tend to avoid any library that handles allocation failure in that way, as it narrows my options.

If an assertion fails, abort the process.[/quote]
Not sure if you're suggesting assertions and exceptions are the same thing, but if you are I disagree. The occurrence of exceptions should typically be beyond the control of the programmer. Assertions are the opposite -- they're caused (deliberately) by the mistaken programmer.

The reason is that it's too easy to write C++ code that leaks or has undefined behavior in the presence of exceptions.[/quote]
I agree it takes more discipline to use exceptions truly correctly and it's something that should be factored in to a decision. Though probably less effort than is required in say, Java, where you end up repeatedly putting the same code in finally blocks that would appear once in a C++ destructor. I heard they were going to do something about this though, in a similar vein to C#'s "using" and python's "with". Not sure if they have yet.

For example when you write some templated code and casually assume that the operator=(const T&) won't throw an exception. This happens all the time. If you want to deal with exceptions you have to put try/catches in lots of places.[/quote]
Actually, I'd say truly exception-safe code contains very few try/catch blocks, as its built out of other exception safe classes, eliminating the need.

If allocation fails, kill the process. You can't do anything about it anyway.[/quote]
Again, this really depends on the application domain.

You don't want fine-grained error recovery information, because error-recovery logic is error-prone. You just want "kill the process," or "stop doing this." Keeping the process alive risks breaking the user's data. Once something unexpected happens the entire process is untrustworthy.[/quote]
I think this is throwing the baby out with the bath water. Killing the process risks losing hours of the user's work, or aborting the program while a back-up is being saved. I would agree if we were talking purely about Windows SEH, perhaps, but they're a different beast and communicate something different to standard C++ exceptions.

Exceptions are generally of the form "Something broke, here's a string message in case you want to log it."[/quote]
That does seem to be rather frequent, though if exceptions are adopted, I would avoid doing that and make them more useful.

And then you can't forget to handle certain types of exceptions,[/quote] but you can just as easily forget to handle certain values of return code.

Return codes, in the places where you want them, are not actually strenuous.[/quote]
I agree. In fact, I don't want this reply to come across as "pro-exception", because they do take a lot of care to use correctly -- more so than simple error codes, I think. But I just wanted to say that the kill-the-process rule is commonly not applicable in the kinds of applications in which C++ is used.

I'm guessing you might be an erlang user, actually(?). But in that case the processes you're talking about are of a different ilk and error handling does warrant a different strategy.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.