When use what Error-Handling

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

Recommended Posts

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.

Thanks,
Xaser

Share on other sites
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.

Share on other sites
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

Share on other sites

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.

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.

Share on other sites
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

Share on other sites
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)

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

Share on other sites

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.

Share on other sites
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

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?

Share on other sites
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.

Share on other sites
Ah okay yea I think I was to much in C# there.. ^^

Thanks,
Xaser

Share on other sites

In C++, exceptions should never be used. Ever. At all.
[/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.

Share on other sites
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

Share on other sites

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.

Share on other sites
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?

Share on other sites

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.

Share on other sites

[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.

Share on other sites
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?

Share on other sites
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.

Share on other sites

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.

Share on other sites
I have few basic rules. Fail fast, fix fast.

If the error value is from user input, throw exception and tell the user how and where it is invalid. It may be input from keyboard, XML or any configuration file, graphics file, basically any content feed into the program.
Also if the error value is from composition of classes, more precisely dynamically composing the classes, assert and after that throw exception. It's still in developement.

Assert invariants in constructors. Throw aswell if too paranoid. It's still in developement, put enought checks to catch all errors.

When released, asserts are omitted. There shouldnt be any exceptions coming from data or logic errors. At this point, depending if it's required, you can disable exceptions.

I dont understand this exception bashing, from STL anyway. I've never released a product where I'm masking any exception, nor there should be many in released product. It's just so much faster to develop with them on.

Share on other sites
There may have been a sprinkle of facetiousness there, but perhaps a simple flow-chart will do:

[font="arial, verdana, tahoma, sans-serif"]*Does your whole team completely grok the different levels of exception safety[/font][font="arial, verdana, tahoma, sans-serif"]?[/font]
[font="arial, verdana, tahoma, sans-serif"]*At a glance, can you look at any function in your code base and determine which particular level of guarantee it provides?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Is it possible to look at any function in your code base and determine which, if any exceptions it's allowed to throw?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Are you using RAII?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Are you not[/font][font="arial, verdana, tahoma, sans-serif"] targeting a games console?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Do you not [/font][font="arial, verdana, tahoma, sans-serif"]care about generated code size and efficiency?[/font]
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"]If you answered 'yes' to all of the above, then C++ exceptions might be ok for you. If you answered 'no' to any of them, then it's a red flag.[/font]

[font="arial, verdana, tahoma, sans-serif"]
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.[/quote]...or use [font="Courier New"]new(nothrow) T[/font]
[/font]
IMHO though, code that relies on new/malloc is almost as bad as code that relies on global variables, but that's a different discussion...
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]

[/font][/font][font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]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.
I think a lot of people have taken this the wrong way. It does sound like a very extreme thing to say, yes...[/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]However, a failed (system-level) allocation is generally a bug -- when you choose C++, you're stating that you're ok with manually managing resources. This means you have to know your memory requirements, and work within them. In 'normal' software, you might be able to compartmentalise things, so that just some particular feature is disabled when there's not enough memory to support it, but in games software, you generally have a fairly fixed memory requirement, and any overflow of that requirement is a developer error.[/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]If there's a bug in your shipping build, you can either try to write fault-tolerant code that will limp on (using exceptions or otherwise), or you can crash and submit a crash report.[/font][font="arial, verdana, tahoma, sans-serif"] [/font][/font][font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]Whether you use exceptions or not doesn't change the fact that you've got a bug, and doesn't change your options with regards to whether you limp or crash![/font][/font]
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).
The main difference is that returned errors are visible in the function signature. In C++, a function could be throwing 6 different types of exceptions, and the signature is allowed to not tell you. That means that when you call those functions, they 'pollute' your new function with the same uncertainties. Java and C# on the other hand, don't suffer from this problem. On smaller or more disciplined teams, this deficiency might not be a problem for you, but it's still a deficiency that C++ forces you to deal with.

Share on other sites

The main difference is that returned errors are visible in the function signature. In C++, a function could be throwing 6 different types of exceptions, and the signature is allowed to not tell you. That means that when you call those functions, they 'pollute' your new function with the same uncertainties. Java and C# on the other hand, don't suffer from this problem. On smaller or more disciplined teams, this deficiency might not be a problem for you, but it's still a deficiency that C++ forces you to deal with.

I'm not sure I see why Java or C# differ in this respect? How can you tell from looking at the function which exceptions the function could potentially throw? Or am I missing something - I'm certainly no expert in C# or Java.

Share on other sites

Does your whole team completely grok the different levels of exception safety?
*At a glance, can you look at any function in your code base and determine which particular level of guarantee it provides?
*Is it possible to look at any function in your code base and determine which, if any exceptions it's allowed to throw?
[/quote]
I believe these points are equally valid when applied to return codes. There is something in the idea that exceptions are "hidden", but this is somewhat counterbalanced by the "ignorability" of return codes.

Neither is perfect.

*Are you using RAII?
[/quote]
Yes.

*Are you not targeting a games console?
[/quote]
No.

*Do you not care about generated code size and efficiency?
[/quote]
For the kind of code that I use exceptions for, nope =]

Overall I think there are places where return codes are preferred, and other places where I'd only use exceptions.

Share on other sites
I think the real problem with exceptions is that there are a lot of misconceptions about them, many people use them wrong, and exception antagonists keep repeating their mantra over and over again, some of them not really understanding why. Funnily, you usually "STL is evil" and "exceptions are evil" in the same session (such as on e.g. GDC 2004).
• On no-crap implementations, exceptions do not have a serious overhead or enlarge your code in a really significant manner if you do not very seriously abuse them.
• Yes, yes, I know... it is one of those mantras that people keep repeating, but it's nevertheless wrong.
• On some consoles both exception handling and the STL implementation suck. As does the C runtime and the allocator. Right. Get over it.
• Exceptions on a compiler that doesn't suck are nowadays on the order of a dozen clock cycles each when entering and exiting the try block. As long as no exception is thrown, you will not be able to measure the difference.
• If you can measure a difference, use a compiler that doesn't suck.
• Exception handling adds maybe a hundred kilobytes to your executable size (30-40kB stub, and then some for each try/catch block). If that is prohibitive, then a lot of other things are equally prohibitive.
• Exceptions are exceptional, not expectional. If there is a very reasonable chance (say, 30%) that a function will fail, consider returning an error code instead.
• If you enter/leave a try block within an inner loop and experience performance issues, don't be surprised. That's not something particular to exceptions, however. 12 clock cycles a billion times is 12 billion cycles, no surprise really. Just don't do that kind of thing. You don't want to call a virtual function from within an inner loop either.
• if you have ten thousand try/catch blocks in your sources, you are doing something wrong. Don't complain about having to write so many of them, write fewer.
• Throwing exceptions can take an unpredictable long time (in theory, minutes, hours, or days) depending on the call stack. Usually, it's something around 200-10000 clock cycles (so, on a desktop, in the lower microsecond range).
• If, on the average, you throw more than 2-3 exceptions per minute, that's already unusual. If you throw more than 10-20 exceptions per second, you're doing something seriously wrong.
• A single-threaded, non trivial OpenGL app with initially ca. 40% CPU load, vsynced to 60fps on a desktop computer which is not the fastest machine in the universe, just about tilts the frame time when throwing and catching 5,000 exceptions per frame. Who really needs thousands of exceptions per frame?
• If your expectation to exceptions is "print a message and exit, because what can you do anyway", you should not use them. There is exit() for killing the process. Though inevitable in many situations, the intended use of exception handling is not to kill the process, but to retreat to a state from where you can resume normal operation. And, sometimes, this is possible in a very elegant way using exceptions.