[C++] Logging unhandled exceptions.

Started by
7 comments, last by AvCol 12 years, 11 months ago
I've heard / read that one common use of exceptions is to handle unhandled exceptions in some master try / catch block that aborts the program on fatal excpetions and simply logs all other ones.

So for example you might have:


try
{
// do stuff here
}
catch( ExcWarning exc )
{
log.output( exc.Message() )
}
catch( ExcFatal exc )
{
// cleanup, make a message box etc.
}


For me this just isn't a good Idea. What I don't get here is if you are simply logging warnings / unhandled errors, how do you return control flow back to where it was? It seems like a stupid thing to even attempt to do so am I just misinterperting how people implement exception based loggers?
Advertisement
I think you're misinterpreting it. A system that does what you describe might look like this, inside "do stuff here":


if (an error occurred) {
if (fatal) {
throw ExcFatal("something bad happened!");
}
else {
log.output("Some warning message.");
}
}


Then your catch(ExcFatal exc) will catch the fatal errors but you're not even throwing an exception for the warnings.

You should be catching constant references (assuming you're using C++), using catch (const ExcFatal& exc) .
one common use of exceptions is to handle unhandled exceptions in some master
If you handle an unhandled exception, it's no longer an unhandled exception, right? Using exceptions to handle exceptions doesn't make sense - you can translate one into another, but you can't throw 2 at once.

[quote name='AvCol' timestamp='1303959095' post='4803818']one common use of exceptions is to handle unhandled exceptions in some master
If you handle an unhandled exception, it's no longer an unhandled exception, right? Using exceptions to handle exceptions doesn't make sense - you can translate one into another, but you can't throw 2 at once.
[/quote]

My wording sucks. What I meant was OTHERWISE unhandled exceptions. So some part of your code might say "ok I only care about exceptions of type X and will only attempt to handle those, exceptions of type Y can go further up the try catch chain" and then at the top level of this chain, your whole program runs in a try catch block which says "ok any exceptions that have seeped through and are not marked as fatal I will just log, and return execution to where that exception was thrown, and if its marked as fatal I will attempt to clean up best I can and stop running."

Now, this may or may not be what people mean when they refer to exception based logging, but that was what I was asking about originaly.
Ok. If an exception isn't handled, and does make it all the way up to main, then it is fatal -- the entire program has been irreversibly unwound by that point.

It is common to put a "catch all" up in main to catch and log these, but there's no returning to the throw-location to recover. You're right to question "how do you return control flow back to where it was?", because you can't - this doesn't make sense. I'm guessing you've mixed up two different "I heard's" about exceptions and logging.

BTW, in C++ games programming, exceptions are almost universally shunned. They're much more feasible in other languages like C# or Java though.
In my implementation, I use a Debug class derived from std::exception, and use only that to throw exceptions, and nothing else. Then I use catch(std::exception &e) to trap them (which should also catch most other standard C++ exceptions generated by other implementations). The purpose of the Debug class is to generate logs automatically when an exception is thrown. But generally exceptions are thrown as last resort things, when a critical feature of the game has failed.
Latest project: Sideways Racing on the iPad

Ok. If an exception isn't handled, and does make it all the way up to main, then it is fatal -- the entire program has been irreversibly unwound by that point.

It is common to put a "catch all" up in main to catch and log these, but there's no returning to the throw-location to recover. You're right to question "how do you return control flow back to where it was?", because you can't - this doesn't make sense. I'm guessing you've mixed up two different "I heard's" about exceptions and logging.

BTW, in C++ games programming, exceptions are almost universally shunned. They're much more feasible in other languages like C# or Java though.

Ok, I thought as much, and this is how I had interperted exceptions before speed reading what I did and confusing myself. Quite relieving actually, I was worried that some basic concept was going right over my head.

Why are they universally shunned though? They are more elegant than returning error codes up the stack, and if you stick to RAII principles they should clean up after themselves too. I use them mainly for things like initialization / loading files. Initialization is usually fatal while loading files usually allows whatever was asking for the file to put a dummy file in if lets say it wasn't found, or there was some data missing and so on.
Why are they universally shunned though? They are more elegant than returning error codes up the stack, and if you stick to RAII principles they should clean up after themselves too. I use them mainly for things like initialization / loading files. Initialization is usually fatal while loading files usually allows whatever was asking for the file to put a dummy file in if lets say it wasn't found, or there was some data missing and so on.
That's a topic that will surely generate a holy war thread...

Some reasons I can think of at the moment:


1) Tradition. Nintendo and Sony have traditionally given developers quite terrible C/C++ compilers, where many features of the language either didn't work, or were implemented horribly. Templates and exceptions were greatly affected by this, and any cross-platform code-base unfortunate enough to have to support those compilers would often choose to avoid those features. Even now, Microsoft and Sony still recommend that you use the options for their console's compilers that disable exception support.

2) Writing exception-safe code in C++ is hard. Sticking to RAII makes it sound easy, but there's still all sorts of pitfalls, like objects being left in an inconsistent state (some members being written to prior to a throw, but others that were going to be written after), double-throwing (your raii classes themselves aren't allowed to trigger errors during resource deallocation), etc... It's very easy to create very subtle bugs if your whole team aren't C++ experts (which is usually the case). I've known guys who've been writing games in C++ for a decade who still aren't familiar with many of the language's quirks and subtleties.

3) Performance. It's not a big deal on a modern PC, and not as much of a big deal on modern consoles as it used to be, but most C++ exception implementations cause horrendous amounts of code bloat (large executables). In console environments, large executables can in fact be a real problem for performance. On older consoles, it definately was a huge problem, so this ties into the 'tradition' problem as well.

4) Style. The elegance argument is highly subjective, and attempting to debate it will only incite a holy war. Some people would argue that manual error codes are more readable, maintainable, etc... especially since C++'s exception specifiers are retarded, which makes reasoning about whether code will throw or not quite a pain when compared to newer languages. Even outside of games, some big players like Google even shun C++ exceptions.

5) Multi-core engines. Standard C++ exceptions aren't clonable, which means passing them across thread boundaries is a pain. In a job-based architecture, the point of invocation and the point of execution aren't actually connected by a call-stack, so handling errors by unwinding the stack doesn't work. If using exceptions, you need a way to catch them in the job handler, clone them onto the heap, and then re-throw them when the job results are collected. You can do this by using your own exception base class, but then the standard exceptions still aren't usable.

2) Writing exception-safe code in C++ is hard. Sticking to RAII makes it sound easy, but there's still all sorts of pitfalls, like objects being left in an inconsistent state (some members being written to prior to a throw, but others that were going to be written after), double-throwing (your raii classes themselves aren't allowed to trigger errors during resource deallocation), etc... It's very easy to create very subtle bugs if your whole team aren't C++ experts (which is usually the case). I've known guys who've been writing games in C++ for a decade who still aren't familiar with many of the language's quirks and subtleties.

3) Performance. It's not a big deal on a modern PC, and not as much of a big deal on modern consoles as it used to be, but most C++ exception implementations cause horrendous amounts of code bloat (large executables). In console environments, large executables can in fact be a real problem for performance. On older consoles, it definately was a huge problem, so this ties into the 'tradition' problem as well.

4) Style. The elegance argument is highly subjective, and attempting to debate it will only incite a holy war. Some people would argue that manual error codes are more readable, maintainable, etc... especially since C++'s exception specifiers are retarded, which makes reasoning about whether code will throw or not quite a pain when compared to newer languages. Even outside of games, some big players like Google even shun C++ exceptions.

5) Multi-core engines. Standard C++ exceptions aren't clonable, which means passing them across thread boundaries is a pain. In a job-based architecture, the point of invocation and the point of execution aren't actually connected by a call-stack, so handling errors by unwinding the stack does


I see that I am walking on thin ice.

2) For me and my code: the rule is constructors, copy constructors, assignment operators and destructors are not allowed to throw, just like they can't pass an error code (other than by reference I suppose). And that one rule gets rid of every single exception based pitfall.

3) I agree, this is a good concern.

4) Ok. I am not going to push anything but to me err = someFunction(); if( err == ERROR_FOR_SOMEONE_ELSE_TO_HANDLE ) return err; *is* unwinding the call stack, isn't it?

5) Interesting, definitely a cause for future concern for me. My current multi threading is Thread 1: input read input and queue message as fast as possible, Thread 2: game state, advance self as fast as possible. Thread 3: Read game state draw everything, play all sounds as fast as possible. Thread 4: Nothing for now. Might be a naive architecture but its responsive and clean.

This topic is closed to new replies.

Advertisement