When use what Error-Handling

Started by
23 comments, last by ApochPiQ 12 years, 9 months ago
Ah okay yea I think I was to much in C# there.. ^^

Thanks,
Xaser
Advertisement


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

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]

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?

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 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/
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?
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.

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.

This topic is closed to new replies.

Advertisement