Hacker News new | comments | show | ask | jobs | submit login

Some more Carmack tweets on C++

IMO, good C++ code is better than good C code, but bad C++ can be much, much worse than bad C code. 10:17 AM Oct 6th

I wish C++ had a "functional" qualifier to enforce no global references or side effects. 11:16 PM Sep 21st

Reading More Effective C++. I have grown to appreciate at least some uses of most C++ features, but I still don't buy in on exceptions. 10:26 PM Aug 13th




I'm no C++ fan (anymore), either, but I don't understand Carmack's taking exception with exceptions. If the alternative is guarding every function call with an if statement to check for error codes, I'll take exceptions almost every time. (Many game programmers I know think that checking for errors is usually bad game programming style, period, but I'm assuming Carmack isn't one of those people.)

Exceptions do have possibly deleterious effects on performance, but then, you can just be judicious about where you use them. Exceptions are at least appropriate for catastrophic failures like, "Oops, the game just crashed," or, "Your network connection died." Thinking about manually unwinding out of a game-over failure like that makes me grimace.

Sigh, game programmers. :)


You're making two mistakes: One about how exceptions work, and another by not considering our constraints.

Exceptions don't just add an overhead when they're thrown, they add overhead for every function call. How else would the exception-handling runtime know to unwind the stack to the correct addresses? It is this performance degradation that we avoid. Why? For the PC, it doesn't really matter. The XBox360 doesn't handle the performance hit very well. The compilers for the Nintendo DS (edit: and the Wii) don't even support exceptions, and the PSP is so crazily architectured that things like floating point numbers or exceptions can cause a framerate to drop from 300 to 12. I've not worked on the PS3. You must understand that these consoles and handhelds are designed very differently from PCs, and are never as powerful.

We do check for errors, we just stay away from exceptions. And dynamic casting, when we can (the DS does a freaking string compare against the vtable!).


> Exceptions don't just add an overhead when they're thrown, they add overhead for every function call. How else would the exception-handling runtime know to unwind the stack to the correct addresses?

With unwind descriptors/exception handling tables, which can be stored in a read-only segment of the executable and don't need to be paged in until an exception occurs. They have no runtime performance cost until an exception is thrown.

Unwind tables are the default exception model on most modern gcc C++ targets. They're also the default exception model in Visual C++ x64, at least.

c.f.:

http://stackoverflow.com/questions/318383/exception-handling...

http://msdn.microsoft.com/en-us/library/1eyas8tf(VS.80).aspx


Yes, that's fair for the PC (I was aware of this for the PC, which is why I said exceptions don't really matter on the PC). But (from memory) I don't believe the Xbox allows for this, even though it uses a very similar variant of the same compiler. It's a moot point, though, for all the other reasons I mentioned. When you've got a cross-platform codebase - and there are precious few platform-exclusive codebases these days - it is far easier to turn exceptions off across the board than deal with individual platform's quirks.


Throwing an exception is a misnomer. An exception doesn't necessarily mean that an error happened. It means, "please unwind the call stack". Unwinding the call stack in a game is a no-no.

Games, especially the simulation, must be deterministic. An exception would most likely destroy determinism.

The simulation/AI is a big, hairy, ugly set of if/else and switch statements anyway. Adding extra error checking is not an extra burden.

The visualization sends asynchronous commands from the CPU to the GPU. The error condition won't be known right away. Querying the GPU will cause a flush of your command stream and ruin performance.

Dynamic memory allocation is reduced to a minimum during a frame so you don't get out of memory errors. Strings are allocated in a pool. Network sockets, file handles, and other performance intensive resources are not created mid-frame.

Exceptions are not a win for games. It is usually best to run the entire frame and then check for errors all at once at the end.


> Unwinding the call stack in a game is a no-no.

For the most part, I agree, which is why I said they're probably only appropriate in games when used judiciously, e.g., for fatal errors.


Exceptions are actually nice for single methods, even in a game.

  void    Horse::ImOnA()
  {
    try
    {
       Giddyup();

       if (IsDead())
         EX_ERROR("Guess I wasn't on it.");

       if (IsFlippingTheFuckOut())
         EX_WARN("Safety hazard!  Get the f outta the way!");
  
       Feed();
    }
    catch (CException& ex)
    {
      ex.Process("Horse::ImOnA() - ", NO_THROW);
    }
    CleanupAfterHorse();
  }
If the horse is dead, then "Horse::ImOnA() - Guess I wasn't on it." will be printed to the console.

If the horse is acting like my mother, then "Horse::ImOnA() - Safety hazard! Get the f outta the way!" will be printed to the console.

Etc.

So this is a nice way to report errors. It's basically a typed goto. But don't you dare try to use it for flow control! >:(

You can report various "levels": EX_DEBUG, EX_WARN, EX_ERROR, EX_FATAL

ex.Process() checks what kind of exception it is. And for EX_FATAL, for example, it will terminate the application (after writing the message to a log file).

It's quite nice to use exceptions in this way, because you rarely ever need to even think about making your methods "exception safe" at all.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: