Progress isn’t made by early risers. It’s made by lazy men trying to find easier ways to do something.

C vs C++ for Game Engine Code

Posted: May 12th, 2013 | Author: | Filed under: Soapbox, Tutorials | 2 Comments »

Until a year or so ago, I was a C++ purist. This was fair, because as a game programmer, generally you would want your code in C++ 99% of the time. The object-oriented style it promotes helps a lot in scalability and code management. Any super-time-critical code would mostly be handled by the engine anyway. Also, as a superset of C, there’s technically no reason why it can’t do everything that C can do. However, coming across game engine code written in C, most notably in HGE, Orx and RK, was an eye opener. There is a poetic simplicity and succinctness to the code. Just reading the code alone makes you instinctively feel that it should be faster, even if just by a smidgen. Much of this past year was spent ping-ponging between the two concepts, trying to figure out which one I like better.

Why C++ instead of C

Readbility

Namespaces are wonderful to have. Ultimately all you want to do is prevent naming collisions. C gets around this by generally having absurdly long function names like  BlahBlahEngine_GetIrritatingObject() which ultimately makes the code harder to read. In C++ GetIrritatingObject() is simple and to the point.

Operator overrides are also very nice when used sensibly. Case in point: which is easier to read?

//C Code
Vector a, b, c, d;
Vector_Set(a, 1, 2, 3);
Vector_Set(b, 3, 4, 5);
Vector_Set(c, Vector_Add(a, b));
Vector_Set(d, Vector_Mul(c, 3));

// C++ Code
Vector a(1, 2, 3);
Vector b(3, 4, 5);
Vector d = (a + b) * 3;

This readability can also be a detriment if badly used, because the programmer is not made aware of the cost of each operation. This, however, can be circumvented by simply having more alert programmers! 😀

RAII

Resource acquisition is initialization. This is one of the little tricks used by smart pointer classes to ensure that memory is cleaned up after them. In general, I don’t like smart pointers. Sure, they are a convenient way to manage memory, but abuse of them tends to lead to bad architecture (more on this later). However, I do like to write almost all my classes using the RAII paradigm. While it has the side-benefit of making your code exception-safe, it also encourages what I consider to be good architecture—in that everything is allocated at once (and therefore most likely in the same area of memory), and no strange allocation happens through different code paths.

C generally uses Init and Deinit functions. These are not exception-safe (i use the term liberally), so if your application gets shut down unexpectedly half-way, it will most likely leak memory. Some OS’s might sandbox you, but not all do.

Templates

Love or hate them, they are still useful for all sorts of things, most notably container classes. Their shortcomings are well documented—hard to debug, inconsistency between compiler implementations, etc. I equate them to C macros, but with more use cases and potential problems. Overall, they are a net gain, but I would still shy away from STL/Boost unless I know their exact implementation. I tend to use these more for my own custom classes, but the moment ugly template magic starts happening, I take pretty much any other alternative I can.

Why C instead of C++

Object vs Data Orientation

We all know object oriented code. After all, it is what was promoted when we were in school, even though at the time, we all secretly wanted to write everything in one big function and be done with it. The structure it enforces produces very scalable and flexible code. It also had the benefit of localizing all data for an object in the same spot of memory, which was and still generally is a good thing.

Data oriented code is the new (relatively speaking) paradigm of envisioning code modules as data manipulation. You take in an input and produce an output. By cleverly organizing your data in a somewhat unobject-like way, you can have the same operation acting on mass amounts of data that happen to be all located sequentially next to each other. As a wise man once summarized, you organize your data as a struct of arrays, rather than an array of structs. The benefit of this is that your data is more cache-optimized, and also lends itself more easily to parallel processing.

Now, it should be noted that both C and C++ are capable of implementing both paradigms, even at the same time (they are not mutually exclusive). However, by its nature, C++ nudges you to think in a more object-oriented way. C doesn’t nudge you at all, and has absolutely no qualms to letting you chop off your own foot if you so desire. It is, however, somewhat easier to head down the data-oriented route without the uneasy feeling that you are doing something naughty. I found the best way to not feel guilty is simply to stop feeling guilty.

The Singleton Problem

This is one of my biggest peeves with object-oriented languages. Let’s say you want to write an audio module, with functions like PlaySound(soundid) that everybody and their grandmother wants to call at some point. OO logic dictates that there should be some kind of SoundManager class, and since there should be only one of them, the Singleton pattern is followed. Thus every time you want to play a sound, you would do something like SoundManager::GetSingletonPtr()->PlaySound(soundid). Depending on your implementation, GetSingletonPtr could contain a branch to check if the singleton exists. Also, managing when your singleton object gets allocated, intiated, etc. is a pain. Of course, there are at least two dozen singleton variants that get around this in various ways. But that’s a lot of reading…

C, on the other hand, is made for modules. By embracing the “evilness” of a globally defined function, one can simply call SoundManager_PlaySound(soundid). Firstly, it’s as simple as just calling a function.  In fact, it is just calling a function! Secondly, your module would have already been inited and deinited at startup and shutdown giving you unequivocal control over the lifespan of the SoundManager.

I get around this by embracing the “evilness” of global objects. All these objects’ lifetimes are managed by a single RAII manager class, but pointers to them are available in the global scope. SOUNDMANAGER->PlaySound(soundid) is not bad, though still not as “clean” as the C way.

Convolution in the Name of Safety

C/C++ was generally regarded as one step up from assembly. When you want finer-grain control over what happens where, without explicitly dictating each opcode, they would be your languages of choice. Lately, C++ has been moving away from that shaky ground. The additional functionality like smart pointers, dynamic casts, templatized algorithms and libraries, are all there in essence to prevent you from screwing yourself over. The cost to that is that you are removed further from implementation details. When I malloc() something, I get a chunk of memory, When I new() something, I get an object, with it’s constructor called, and whatever happens in there. This is not a bad thing, and probably improves the quality of life for C++ programmers in general. The thing is, if you are going in this direction, why not use a language like Java or C# which was designed for this role in the first place?

Instead of writing code to prevent programmers from doing stupid things, why not have smart programmers that won’t do stupid things in the first place? Game programmers, especially engine programmers, are control freaks when it comes to what their code does. Sometimes, we do want to shoot our own foot off (because it’s amusing?), so let us and stop asking questions!

I get around this by simply not using the C++ features I don’t like. Yes, I use new and delete, since I know what they do (as long as they are my own classes). I use sprintf instead of stringstreams which are just clunky. I use arrays instead of vectors. I use the C string manipulation functions or simply roll my own instead of std::string. I use C casts instead because it is briefer, and architect so I won’t be in a position to cast the wrong pointer. That last one is particular poignant. With all the convenience tools taken out, you are forced to look at your architecture, design it sensibly, and generally be more aware of the different interactions. (The same can be said for avoiding VisualAssist, but that’s a rant for another day!)

 


2 Comments on “C vs C++ for Game Engine Code”

  1. 1 Alex said at 12:36 am on July 9th, 2013:

    An interesting topic. I haven’t touched C/C++ for a while, mostly because the past projects were based on something different.
    I think one of the main things to consider would be time. If you can afford to put more time into the project, investing in C code may be beneficial in certain situations. Or if you’re planning to make custom libraries and reuse them later in the future projects.

    Frankly, I’m more worried by various frameworks/SDKs. They can quickly destroy all of your preciously obtained extra control/performance. I’m still having nightmares about how Unity3D handles particles. If you want to override their behaviour in some way, you have to copy the entire array from particles manager, make the changes and then copy it back *sigh*

    Another thing is silly programmers. Sometimes I think they’re the main reason why the focus is shifting to less volatile languages/frameworks nowadays. As an example, I’ve seen code to move UI buttons around the screen recently. The logic was to pull current coordinates of a button, calculate new coordinates, destroy the old button and create a new one with the new coordinates. Many times per second. Don’t think any amounts of C will help there 🙂

    Enough of the sad stuff. I have a question to ask regarding the data oriented approach. Do you think a component based engine can be organised to work under such paradigm? Or the components are too big (let’s say a physics component) and need to be simplified? What’s the optimal unit of processing, in other words?

    And thanks for the article ;P

  2. 2 Eugene said at 7:26 am on July 9th, 2013:

    Alex, regarding your thoughts about frameworks, it’s just an indication that you are going as a programmer. When we start out, we use high level frameworks because we have no clue what’s going on. Then we start finding stuff we think can be done better for our specific game and want more control, and hence go lower level. Before you know it, you’re rattling off assembly (maybe! I haven’t gotten there yet).

    Regarding your question, yes I absolutely do think it is possible (even optimal), and mean to experiment with that approach. While I’m not a data-oriented guru (yet!), my current belief is that the benefit of OO is not scalability (the opposite is true in fact), but instead flexibility. Unlike OO, you’d tend to spend a lot more time pre-planning to figure out how everything fits together optimally rather than it falling into place intuitively and tweaking it. Or maybe that’s just because I’m new to the paradigm myself! 😛

    Be warned though, that people more experienced and knowledgable and I have claimed they have never seen a component-based system work well. I mean to test that claim.


Leave a Reply