8
\$\begingroup\$

What are the pros and cons of using Exceptions in C++ in relation to game development.

Google style guide says that they don't use Exceptions for a variety of reasons. Are the same reasons pertinent towards game development?

We do not use C++ exceptions... - google-styleguide.googlecode.com

Some issues to think about.

  • How it pertains to the development of a libraries used through multiple projects.
  • How does it affect unit testing, integration testing, etc?
  • What does it do to using third party libraries.
\$\endgroup\$
  • 3
    \$\begingroup\$ Oddly no one had any pros \$\endgroup\$ – David Young Aug 18 '10 at 0:21
7
\$\begingroup\$

There are also some nasty consequences for using exceptions in C++ if you don't know all the details of how they work. Since many games are written in C++ this may be a factor.

For example, in C++ throwing an exception in a destructor can have serious consequences. It can cause all kinds of undefined behavior and crashes, because you can get trapped in a situation where you have more than one active exception in flight. (See Item #8 of Effective C++ for more info on this.)

\$\endgroup\$
  • 4
    \$\begingroup\$ +1 for Effective C++. Reading the 3rd edition as we speak. Exception are fine with me, as long as you're very strict about where you use them, where you catch them and you can guarantee that any 3rd party code does the same. \$\endgroup\$ – Anthony Aug 15 '10 at 23:29
9
\$\begingroup\$

Joel on Software's views of Exceptions.

Interesting view not currently in any of the answers,

The reasoning is that I consider exceptions to be no better than "goto's", considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto's:

  1. They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn't reveal potential bugs.

  2. They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don't catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn't think about.

http://www.joelonsoftware.com/items/2003/10/13.html

\$\endgroup\$
  • \$\begingroup\$ Halleluja... Great reference. i've had an aversion with exceptions for exactly this reason, but apparently it is the preferred way of doing things nowadays. Great to see a nice article which gives another view \$\endgroup\$ – Toad Aug 15 '10 at 17:58
  • 1
    \$\begingroup\$ +1 because exceptions have a much too high risk to fail late, i.e. when the game's already shipped. \$\endgroup\$ – martinpi Aug 16 '10 at 9:51
  • \$\begingroup\$ I completely agree with point number 2. I won't fight against languages that use exceptions throughout, but neither do I consider them the 'right' way to handle error conditions either. \$\endgroup\$ – Kylotan Aug 17 '10 at 16:32
5
\$\begingroup\$

Exceptions are not supported and therefore highly discouraged in at least one modern console development environment.

Most console developers I know prefer not to use them anyway due to added overhead in the executable and the philosophy that they're just plain not needed. RTTI is viewed the same way.

\$\endgroup\$
  • \$\begingroup\$ Which console is it not supported on? \$\endgroup\$ – David Young Aug 15 '10 at 2:59
  • \$\begingroup\$ I'm trying to be coy because of NDAs, even though it's been revealed more specifically on SO before. \$\endgroup\$ – Dan Olson Aug 15 '10 at 4:41
2
\$\begingroup\$

Of all the projects I worked on in games, none of them used exceptions. Function call overhead is the major reason. As mentioned before, like RTTI, it's, in most studios, not a subject of discussion. Not because most coders come from a Java/academics background, because frankly, most don't.
It doesn't affect unit testing really as you just assert on conditions. For libraries, you're better off without exceptions as you'll force it upon everyone that uses it.
While it makes some situations slightly harder to handle, it also (imo) forces you to have a more controlled setup, as exceptions can lead to abuse. Error tolerance is nice, but not if it leads to sloppy coding.
Mind you, this comes from someone who never used exception handling (well okay, once or twice in tools - it didn't do it for me, I guess it's what you grow up with).

\$\endgroup\$
  • 1
    \$\begingroup\$ You're right, but unfortunately rather than use alternative error-handling, most games programmers just revert to crashing or returning NULL (which will probably lead to a crash anyway). We've not come up with any decent replacement. \$\endgroup\$ – tenpn Aug 15 '10 at 9:58
  • \$\begingroup\$ Returning NULL, or an error code, is a perfectly reasonable replacement so long as you check the return codes too. \$\endgroup\$ – JasonD Aug 15 '10 at 11:56
  • \$\begingroup\$ Yeah but most games programmers don't. Which is my point. \$\endgroup\$ – tenpn Aug 15 '10 at 13:02
  • 1
    \$\begingroup\$ It's a problem of the C++ language as much as anything - if you see a line of code that says function(); you don't instantly know whether an error code is being ignored or not. This is presumably why languages that let you ignore a return value try and coerce you into preferring exceptions. \$\endgroup\$ – Kylotan Aug 17 '10 at 16:35
  • 1
    \$\begingroup\$ The enforcement of error arguments is no longer an issue. Modern code that does not use exceptions relies on an Error class that is lightweight and if ignored results in an assertion being fired. So it really cannot be ignored no more than an exception can be ignored. \$\endgroup\$ – Samaursa Nov 3 '15 at 3:13
1
\$\begingroup\$

The most important thing for me as a professional games developer is that exceptions have to be written by people that understand how exceptions work (which there are few in the games industry) debugged by them too, and the underlying code that runs them (which is a branchy mess anyway and will kill your in-order processor) had to be written by people providing your compiler/linker. The problem with that is that they only have a finite amount of resources, so they concentrate on writing better optimisations and fixes for code that games developers do use... which usually isn't exceptions. If you've got an exception bug on modern hardware with new compilers, who are you gonna call? your exception expert who thinks everything is okay with the code, or the vendor who says, yeah, soon, we're just trying to make intrinsics happen more automatically.

There's nothing inherently wrong with exceptions, just they're not good because reality gets in the way of theory.

\$\endgroup\$
0
\$\begingroup\$

To me exception-handling can be great if everything conforms to RAII and you're reserving exceptions for truly exceptional paths (ex: a corrupt file being read) and you never need to throw across module boundaries and your entire team is on board with them......

Zero-Cost EH

Most compilers these days implement zero-cost exception handling which can make it even cheaper than manually branching on error conditions in regular execution paths, though in exchange it makes exceptional paths enormously expensive (there is also some code bloat from it, though probably not more than if you thoroughly handled every possible error manually). The fact that throwing is enormously expensive shouldn't matter though if exceptions are being used the way they're intended in C++ (for truly exceptional circumstances that shouldn't happen in normal conditions).

Side Effect Reversal

That said, making everything conform to RAII is much easier said than done. It's easy for local resources stored in a function to wrap them into C++ objects with destructors that clean themselves up, but it's not so easy to write undo/rollback logic for every single side effect that could occur in the entire software. Just consider how hard it is to write a container like std::vector which is the simplest random-access sequence to be perfectly exception-safe. Now multiply that difficulty across all the data structures of an entire large-scale software.

As a basic example, consider an exception encountered in the process of inserting items to a scene graph. To properly recover from that exception, you might need to undo those changes in the scene graph and restore the system back to a state as though the operation never occurred. That means removing the children inserted to the scene graph automatically on encountering an exception, perhaps from a scope guard.

Doing this thoroughly in a software that causes many side effects and deals with a lot of persistent state is much easier said than done. Often I think the pragmatic solution is to not bother with it in the interest of getting things shipped. With the right kind of codebase which has some kind of central undo system though and maybe persistent data structures and the minimal number of functions causing side effects, you might be able to achieve exception-safety across the board... but it's a lot of stuff you need if all you're going to be doing is trying to make your code exception-safe everywhere.

There's something practically wrong there because conceptually side effect reversal is a difficult problem, but it's made more difficult in C++. It's actually easier sometimes to reverse side effects in C in response to an error code than it is to do it in response to an exception in C++. One of the reasons is because any given point of any function could potentially throw in C++. If you're trying to write a generic container working with type T, you can't even anticipate where those exit points are, since anything involving T could throw. Even comparing one T with another for equality could throw. Your code has to handle every possibility, and the number of possibilities multiply exponentially in C++ whereas in C, they are much fewer in number.

Lack of Standards

Another problem of exception-handling is lack of central standards. There are some like hopefully everyone agrees that destructors should never throw since rollbacks should never fail, but that's just covering a pathological case that would generally crash the software if you violated the rule.

There should be some more sensible standards like never throw from a comparison operator (all functions which are logically immutable should never throw), never throw from a move ctor, etc. Such guarantees would make it so much easier to write exception-safe code, but we have no such guarantees. We have to kind of go by the rule that everything could throw -- if not now, then possibly in the future.

Worse, people from different language backgrounds look at exceptions very differently. In Python, they actually have this "leap before you look" philosophy which involves triggering exceptions in regular execution paths! When you then get such developers writing C++ code, you could be looking at exceptions being thrown left and right for things that aren't really exceptional, and handling it all can be a nightmare if you're trying to write generic code, e.g.

Error-Handling

Error handling has the disadvantage that you have to check for every possible error that could occur. But it does have one advantage that sometimes you can kind of check for errors slightly late in hindsight, like glGetError, to significantly reduce the exit points in a function as well as making them explicit. Sometimes you can keep running the code a little bit longer before checking and propagating and recovering from an error, and sometimes that can genuinely be easier than exception-handling (especially with side effect reversal). But you might not even bother with errors so much either in a game, maybe just shutting the game down with a message if you encounter things like corrupt files, out of memory errors, etc.

How it pertains to the development of a libraries used through multiple projects.

A nasty part of exceptions in DLL contexts is that you cannot safely throw exceptions from one module to another unless you can guarantee that they are built by the same compiler, use the same settings, etc. So if you're writing like a plugin architecture intended for people to be used from all kinds of compilers and possibly maybe even from different languages like Lua, C, C#, Java, etc., often exceptions start to become a major nuisance since you have to swallow every exception and translate them to error codes anyway all over the place.

What does it do to using third party libraries.

If they're dylibs, then they cannot throw exceptions safely for reasons mentioned above. They'd have to use error codes which also means you, using the library, would have to constantly check for error codes. They could wrap their dylib with a statically-linked C++ wrapper lib you build yourself which translates the error codes from the dylib into thrown exceptions (basically throwing from your binary into your binary). Generally I think most third party libs shouldn't even bother and just stick to error codes if they use dylibs/shared libs.

How does it affect unit testing, integration testing, etc?

I generally don't encounter teams testing exceptional/error paths of their code so much. I used to do it and actually had code that could properly recover from bad_alloc because I wanted to make my code ultra-robust, but doesn't really help when I'm the only one doing it in a team. In the end I stopped bothering. I imagine for a mission-critical software that entire teams might check to make sure their code robustly recovers from exceptions and errors in all possible cases, but that's a lot of time to invest. The time makes sense in mission-critical software but maybe not a game.

Love/Hate

Exceptions are also my kind of love/hate feature of C++ -- one of the most deeply-impactful features of the language making RAII more like a requirement than a convenience, since it changes the way you have to write code upside down from, say, C to handle the fact that any given line of code could be an implicit exit point for a function. Sometimes I almost wish the language didn't have exceptions. But there are times when I find exceptions really useful, but they are too much of a dominating factor to me in whether I choose to write a piece of code in C or C++.

They would be exponentially more useful to me if the standard committee really focused heavily on establishing ABI standards which allowed exceptions to be safely thrown across modules, at least from C++ to C++ code. It would be awesome if you could safely throw across languages, though that's kind of a pipe dream. The other thing that would make exceptions exponentially more useful to me is if noexcept functions actually caused a compiler error if they tried to invoke any functionality that could throw instead of just calling std::terminate on encountering an exception. That's next to useless (might as well call that icantexcept instead of noexcept). It would be so much easier to ensure that all destructors are exception-safe, for example, if noexcept actually caused a compiler error if the destructor did anything that could throw. If that's too expensive to thoroughly determine at compile-time, then just make it so noexcept functions (which all destructors implicitly should be) are only allowed to likewise call noexcept functions/operators, and make it a compiler error to throw from any one of them.

\$\endgroup\$
-2
\$\begingroup\$

As far as I see it there are two main reasons it's not traditionally used in game development:

  • Most game programmers are either graduates, who learned on Java and Haskell, or C programmers, who don't have any experience of exceptions. Hence they're quite afraid of them and don't know how to use them correctly.
  • The stack unwinding when an exception is thrown has performance implications (although this is kind of received wisdom, and some references would be great).
\$\endgroup\$
  • 3
    \$\begingroup\$ Java programmers not knowing how to use Exceptions? Java is based around exception handling. Even the most basic of Java programmers understand the Try, Catch, Finally blocks. You really can't program in Java without either throwing Exceptions or handling them. \$\endgroup\$ – David Young Aug 14 '10 at 22:33
  • 4
    \$\begingroup\$ Its more then just stack unwinding. Exceptions add overhead to every function call. codeproject.com/KB/cpp/exceptionhandler.aspx \$\endgroup\$ – Jonathan Fischoff Aug 14 '10 at 23:09
  • \$\begingroup\$ @David Young I'm talking more about the strong/weak guarentees, and the more complex side of using exceptions, that university graduates may not be experienced in. To compound that, many universities are much more interested in how you structure your OOP so put less emphasis on learning how to use Java exceptions properly. But you're right I wasn't clear. \$\endgroup\$ – tenpn Aug 15 '10 at 9:55
  • \$\begingroup\$ Also, maybe "afraid" is the wrong word. :) \$\endgroup\$ – tenpn Aug 15 '10 at 9:57
  • 3
    \$\begingroup\$ I would say they're often the opposite of "afraid" - if you don't have a strict "no exceptions, no exceptions" rule, most recent Java graduates will end up throwing exceptions more often than returning, just like novice C programmers use goto and break rather than write good loop invariants. \$\endgroup\$ – user744 Aug 15 '10 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.