Sign in to follow this  
Ectara

Portable Use And Disabling Of Exceptions

Recommended Posts

Cornstalks    7030

@Hodgman: while I like option #3, what about situations outside of your control (for example, memory allocation failures)? Granted, these rarely happen in practice, but I feel like not handling them at all is... scary (security exploits?).

Share this post


Link to post
Share on other sites
Hodgman    51221

what about situations outside of your control (for example, memory allocation failures)? Granted, these rarely happen in practice, but I feel like not handling them at all is... scary (security exploits?).

On consoles, memory allocation failures are completely in your control. You know how much memory you're limited to, so you have to design your game to fit in that limit. On PC they practically don't happen, unless you exhaust the address space somehow... You can prevent them causing your game from doing whacky things (like being security exploits) by terminating the app if an allocation fails.

Things like out-of-bounds array accesses can be security exploits, but they don't occur with correct code an sanitized data.
If you're not sure if any of the data in your system is valid, then inside the operator[] function (or any function that does something 'dangerous' with data), you need to perform a range-check/sanity-check every single time (and have some error handling code -- return codes, exceptions, etc)...

Alternatively, you can sanitize the data earlier, when it enters the system as a whole. If the data is trusted to begin with, and it's only transformed by correct code, then you can guarantee that it will continue to be trustworthy for all time without the need to constantly check for errors every time it's used... so, move your (permanent, not-stripped-in-retail-builds) error checking to places where data originates, not where it's used (and put your development-builds-only checking at the usage points for debugging purposes).

If you have genuinely recoverable construction errors, exceptions are pretty much your only option in C++

You can also use a "return code" via an out-argument, instructing the caller to promptly delete the newly constructed object without using it. Edited by Hodgman

Share this post


Link to post
Share on other sites
wintertime    4108

I think one of the worst things in C was always lazy people ignoring error codes and crossing their fingers that nothing would go wrong. Thats because its a pain for the caller to always recheck errorcodes which are different for each function and it slows the program down even if nothing goes wrong, as there is always a check inside a function and outside and possibly many more levels of doubled effort for checking.

Exceptions are just thrown one time and if one likes to ignore them at least the program terminates instead of screwing up more and more, but the user of the function always gets the option of catching and doing a retry, trying a safer way to to it, filling in some standard data or terminating. Having to provide special callbacks for everything to gain this choice as the library user without exceptions is even more of a pain than checking errorcodes and if you want to recover you would then have to longjump around or set some global errorcode and return to the library which then returns to your outside code which then not only needs to check for an errorcode but for your threadunsafe global errorcode and remember to reset it.

Having to precheck everything you feed into a library function call is also painful doubled effort when the user is less likely to know all conditions under which the function accepts the data and also needs to replicate this check around every call, when the library then checks again to be safe from user error, if noone misguided disabled the assertions. If there is no check inside the library it gets inherently unsafe and your whole memory could get scrambled; dont tell me all bugs would always be found before disabling the checks or even without ever providing checks.

If you just feed everything into the library without prechecks you cant just let the library assert because it should be the main programs responsibility to decide how to proceed on errors and not the 'by many people with different usecases used and possibly aquired from somewhere else' library's. IMO there have been too many programs which just abort without a decent error message cause of such librarys and people using them wondering what happened. Quirky, painful to use or painful/impossible to configure error behavior is often the thing which makes me want to not use some library.

Share this post


Link to post
Share on other sites
swiftcoder    18426


If you have genuinely recoverable construction errors, exceptions are pretty much your only option in C++

You can also use a "return code" via an out-argument, instructing the caller to promptly delete the newly constructed object without using it.


And, of course, static factory methods side-step this problem entirely.

Share this post


Link to post
Share on other sites

That said, I'm not a fan of exceptions. [...] They have been termed "invisible gotos", and I don't think that is an unfair characterisation.


Funnily, I'm a fan of exceptions, exactly for that reason. They are "invisible gotos", with the difference that they clean up. This allows you to write very nice and easy (and, efficient) code in some cases.
 

Do you have any examples of a recoverable error that you want to support?


I've written initialization code similar to this before, worked like a charm:
[tt]try { audio = create_audio() } catch(int) { audio = &dummy_audio; }[/tt]<br />
[tt]try { gl = create_gl4() } catch(int) { gl = create_gl3(); }[/tt]

If something goes wrong (no sound card, OpenAL not installed, necessary extension not supported, whatever) the create function just throws. In the first case, the program uses a dummy implementation that doesn't produce sound (could be something else too). In the second case, if creating an OpenGL 4 context with all desired extensions fails, same is tried for an GL3 context, and if that one also throws, exception will make its way up to [tt]main[/tt] where it's caught, an error is logged, and [tt]abort[/tt] is called. Don't care what you have done, stack is unwound, RAII assures that someone cleans up your mess. If anything goes wrong, just throw, forget what you've done, and have a fresh start.

Of course you can do the same without exceptions, too. But it's not nearly as nice. Overhead? Even if there is one, this runs once during the lifetime of the program, so who cares. It's probably a matter of taste, too. But to me, personally, this is a great feature.

 

Imagine another case: A texture is missing. In a development build, this should probably be an [tt]assert[/tt]. This is an obvious error, and it will fail again predictably the next time you launch the game, therefore it should fail hard so you're forced to fix it.

 

On the other hand, this really cannot ever happen when your game is shipped, so you need not check, right? Well, except if the user modifies your PAK files, or deletes something, or for whatever reason a file is lost. What now? Assert or crash? Check a return code every single time for something that really never happens?

 

Enter exceptions. An exception handler could replace the missing texture with a "fail texture" (e.g. red/white checkerboard) and output a message on the console. Is this perfect? Of course not, but the "piss off factor" of still being able to play more or less normally with a broken texture and knowing that something went wrong is much lower than getting "Your application has asked the runtime to terminate it".
 

performance


As for performance on general purpose PCs (I can't talk about consoles), exceptions are neglegible unless you do some very stupid things. If you don't throw ten thousand exceptions per second, it does not matter at all. And if you do, you haven't understood the "exceptional" part of "exception".

SJLJ exceptions admittedly have a fixed overhead for every try block, but the solution is simple: Spend a few seconds of thought before you add a million try blocks. It's the exact same thing as with virtual function calls. If you aren't outright playing stupid, they're not an issue. DW2 exceptions (GCC 4.x Win32 or Linux32/64) and SEH exceptions (MSVC / GCC 4.8 Win32/64) are practically "free".

Edited by samoth

Share this post


Link to post
Share on other sites
swiftcoder    18426

In the second case, if creating an OpenGL 4 context with all desired extensions fails, same is tried for an GL3 context, and if that one also throws, exception will make its way up to main where it's caught, an error is logged, and abort is called.

There is no advantage here over just abort()'ing directly after failing to create the GL3 context. The OS is going to deal with freeing all your resources when you abort(), so why bother unwinding all the way to main?

 

In addition, you are handling the error right when it occurs - you might just as well inspect the return value.

Enter exceptions. An exception handler could replace the missing texture with a "fail texture" (e.g. red/white checkerboard) and output a message on the console.

And so could the code that reads a return value. There is nothing special about exceptions here - the magic is all in the handler. And woe betide the programmer who forgets to write said handler (or doesn't know he needs to)...

 

 

And if you do, you haven't understood the "exceptional" part of "exception".

This is perhaps my biggest pet-peeve about exceptions:

  • Did you write code that checks a condition and then throws an exception on failure? This isn't an exceptional situation, it's an anticipated and error-checked situation.
  • Did you write an exception handler? This isn't an exceptional situation either, it's an anticipated and handled error situation.
Edited by swiftcoder

Share this post


Link to post
Share on other sites

There is no advantage here over just abort()'ing directly after failing to create the GL3 context. The OS is going to deal with freeing all your resources when you abort(), so why bother unwinding all the way to main?


There's nothing wrong with doing a clean exit, and there is nothing wrong with code that is clear and concise. The mere fact that the OS will close your handles and such isn't really an excuse. If it was, you'd just do a [tt]*(void*)0;[/tt] when the user selects "quit" from the menu. That will certainly quit the program, too  :-)

But more importantly, you're (deliberately, as I must assume) neglecting that printing out a one-line-message followed by [tt]abort[/tt] is not what exceptions are meant for (even though this is what most people do anyway, almost all the time).

Aborting the process is the absolutely last thing you do, if there's no other options. If rudely killing the process when "something bad" happens is your intent, you can indeed just as well call [tt]abort[/tt]. You'll have 8 keys less to type for the same effect.

 

Exceptions are for cases where you might have a chance of recovering in some way, or where you might at least have a chance of crashing less hard.

 

Such as trying another thing if the first (which is expected to work most of the time) fails. Or, for example, a program that has been writing to disk might want to exit cleanly even in the presence of a problem that prevents it from continuing -- instead of killing the process and forfeiting the data in the buffers. Or, it might want to delete all open (writeable) files under the assumption that they are possibly incomplete/unusable, and such files shouldn't stay around. The handler doesn't even need to know what went wrong or where, it doesn't care in this case.

 

This could of course also be done without exceptions, but not as cleanly, in one place, with a single handler for a hundred possible reasons.

woe betide the programmer who forgets to write said handler (or doesn't know he needs to)...


True, but... the same is true for return codes, except for one tiny detail. If someone doesn't check return codes, it "works" and doesn't work, and nobody can tell why. Or, it works for a while and then crashes at a completely unrelated occasion because some pointer is NULL. Or, it simply corrupts data and you never find out until 2 months after shipping when your boss starts yelling at you. Or, something different.

If someone forgets to handle exceptions, the application will be terminated with a human-readable message that is (hopefully) somewhat meaninful, at least to the guy who wrote the code. It [i]will not[/i] go unnoticed.
 

This isn't an exceptional situation, it's an anticipated and error-checked situation. This isn't an exceptional situation either, it's an anticipated and handled error situation.


Both are true, and both are silly arguments against exceptions. Not the code that you write is exceptional, but the occurrance of an exception, that is, the condition failing.

 

Exceptions assume that your code and the data is "generally OK" and it "generally works", most of the time, and they are a way of still ensuring that something is done in the [i]exceptional case[/i] when that's not true (and, without making the code unreadable, though what's "unreadable" and what is not is probably a very personal matter of taste, you might not agree on that part).

 

It would be really nice if  I magically didn't have to think about errors ever again, by flipping the "compile with exceptions" switch. But of course, you still have to write code that anticipates (and handles) failure.

Share this post


Link to post
Share on other sites
swiftcoder    18426

True, but... the same is true for return codes, except for one tiny detail. If someone doesn't check return codes, it "works" and doesn't work, and nobody can tell why. Or, it works for a while and then crashes at a completely unrelated occasion because some pointer is NULL. Or, it simply corrupts data and you never find out until 2 months after shipping when your boss starts yelling at you. Or, something different.

Alexandrescu published Mandatory Error Codes way back in 2005, and C++11 move semantics remove the remaining ick factor from his implementation. I'm not sure why this managed to largely fly under the radar for the last few years, but it solves the problem very elegantly - just don't allow your clients to accidentally ignore error codes.

 

(incidentally, he included exception-safe constructors in the same article)

Share this post


Link to post
Share on other sites
Bacterius    13165

But more importantly, you're (deliberately, as I must assume) neglecting that printing out a one-line-message followed by abort is not what exceptions are meant for (even though this is what most people do anyway, almost all the time).


The reason this is often done is because it is cleaner, and much harder to get wrong, than checking error codes after each goddamn function call and selectively figure out what needs to be done upon failure (e.g. send an error code through this and that socket, write something to the log file, delete such and such file, etc..). Bubbling the exception up the call stack provides an elegant and intuitive way of handling this, regardless of whether exceptions were meant for this or not. Many non-trivial applications cannot just dump core and require a complicated, data-dependent approach to failure (for recovery, cleanup, feedback, etc..) which would lead to a lot of duplicate code using error codes alone. 

 

If you want to find out what a modern language using only return codes looks like, check out Golang. The error handling is an absolute mess and makes code incredibly frustrating to read as each function call is accompanied by two or more boilerplate error-checking lines. Just because it works doesn't mean it's perfect, otherwise we would still be using 4-bit assembly "because it works". Though it is a matter of taste, to some extent.

 

As for error recovery, well, it depends on what your program is and how it is to be used. If it's a command-line tool, you probably don't want to waste time trying to guess the user's thoughts and attempt to recover every time. No, you just print out some error, crash, and let the user fix it. On the other hand, if you are writing a GUI utility program, if there's an error you want a dialog box to pop up saying that something went wrong, to let the user try again, rather than crashing to the desktop and letting the user guess where the log file is.

 

Of course, if you run out of memory, or some other general runtime failure, there is not much you can do. Doing anything nontrivial in your error handler will probably cause the program to fail again, sending it crashing and burning, which leads to all sorts of interesting effects, such as, say, deleting your entire home directory wink.png

Share this post


Link to post
Share on other sites

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