By Kyle Wilson
Saturday, July 15, 2006
A number of different on-line forums (most notably the SWEng-GameDev mailing list) have seen recent discussion of whether C++ is a wise language choice for game development. When people have to make that decision in the real world, the dominance of C++ has been overwhelming. But if on-line discussion is any indication, there's also a good deal of dissatisfaction with that choice.
The attacks on C++ come on two flanks. From the low-level side comes the argument that the language supports too high a level of abstraction, and that we should go back to programming in straight C. This argument holds that writing efficient code in C is easier than writing efficient code in C++ because the level of information hiding afforded by C++ hides inefficiencies from cursory perusal. For example, in C, you can read the linea = b + c;
and know that a, b and c are primitive types and that the addition and the assignment will generate, at most, a few lines of assembly code. In C++, a, b and c could be objects of any size calling overloaded addition and assignment operators that might generate still more temporary objects, allocate memory, call sleep(), reformat your hard drive, etc. The only safe way to prevent such shenanigans is to disable compiler support for C++ language features entirely.
The other camp of C++ criticism compares it to higher-level languages and finds C++ wanting. This camp holds that C++ suffers under the burden of backwards compatibility and slow ANSI/ISO standard evolution. It lacks features found in more modern languages, such as aspects, closures, garbage collection, reflection, and native support for concurrency. The paleolithic C++ physical structure--.h include files and .cpp implementation files--results in achingly slow build times and forces programmers to write every function definition twice, once where it's declared in the header and once where it's defined. We would be better off, according to this argument, writing our games in C#, Eiffel or Objective Caml.
Remarkably, the two sides don't even disagree. Instead, the sentiment is commonly expressed that straight C is the way to go for core engine code while less-performance-critical gameplay code should be written in a high-level scripting language (for a cogent expression of this opinion, see this post by Robert Blum in the SWEng-GameDev thread). C++ falls awkwardly in the middle, a jack of all trades and master of none.
Given all this discontent... Why C++? Why has it been so successful, and does the language deserve to see that success continue?
First, I'll get the meta-reason out of the way: Nothing succeeds like success. The fact that C++ is the dominant language for game development gives it a lot of inertia. If you want to hire experienced programmers, it's much easier to recruit C++ gurus than it is to find OCaml talent. If you're going to develop for consoles, Microsoft and Sony provide C++ compilers, but for any other language you're on your own. And if you want to link with middleware, most modern game middleware packages--including Gamebryo, Havok, FMOD, SpeedTree and the Unreal Engine--are written in C++. Developing in another language, even C, is a lonely path.
The power of the C++ network effect is not to be underestimated. For example, Naughty Dog used an in-house LISP variant called GOAL (Game Oriented Assembly Lisp) for the Jak and Daxter titles. They had a significant investment in GOAL, for which they'd written their own compiler, linker and debugger. They supported dynamic reloading of code by a running game and reportedly generated tight, efficient assembly. After their acquisition by Sony, however, Naughty Dog were forced to transition to C++ development. "Sony wants us to be able to share code with other studios, and this works both ways - both other studios using our code and vice versa," posted Naughty Dog lead programmer Scott Shumaker.
But rather than dwelling on the advantages that derive from its existing popularity, I'd like to focus on the inherent strengths of C++ for game development. In doing that, it's important to understand how and why C++ evolved into the language it is today. C++ grew out of C. C was a systems programming language that co-evolved with the Unix operating system and that gained popularity as Unix spread. C was tight, close to the metal. You can look at a C function and tell almost exactly what assembly will result from compilation.
C++ adds layers of abstraction. C++ began as a set of extensions to C to add support for the object-oriented concepts found in Simula, classes and inheritance. As creator Bjarne Stroustrup writes in his C++ FAQ, "Since 1987 or so, the focus of development the C++ language and its associated programming styles have been the use of templates, static polymorphism, generic programming, and multiparadigm programming." C++ continues to evolve to support new design paradigms.
The growth and evolution of C++ has been a mixed blessing. On the one hand, C++ is truly a language for multi-paradigm development: the language supports generic, imperative and object-oriented programming. Any C++ program, or part of a C++ program, can be written in the appropriate style to best model a particular problem domain. On the other hand, C++ has been a victim of its own success. As new core language and standard library features have been added to meet new needs, programming in C++ has become a powerful but complicated business. It's easy to hire people who call themselves C++ programmers, but engineers who understand both the low-level details of the C++ object model and the compile-time power of C++ template metaprogramming are hard to come by. And C++, in the hands of an programmer without a good grasp of the language, is a dangerous thing.
Despite this risk, I think that the answer is better education, not a wholesale rush back to programming in C. Because in the right hands, C++ is a more powerful, safer, and--yes, really--more efficient language than C is.
More powerful? Every C game I've seen has re-implemented virtual function tables with structs stuffed full of function pointers. C++ has native support for object-oriented code. The language's native syntax allows expression that's closer to a programmer's intention. C++ template metaprogramming is vastly more capable than macro coding with the C preprocessor. And the C++ standard library offers a broad array of containers and algorithms. You're likely to be able to implement any given task with less code in C++, from low-level bit shuffling to high-level state logic.
Safer? In C, if we write#define MAX(A,B) ((A) > (B)) ? (A) : (B))
then x will be incremented twice after macro expansion. The inlined templatized C++ std::max() function is equally efficient and free of such unpleasant side-effects. The lack of templates in C encourages passing structs through void pointers, as in the unfortunate standard library qsort() function. This predilection for void* undercuts the protections of the type-safety system and turns easily-caught compile-time errors into hard-to-find runtime errors. In C, the Resource Acquisition Is Initialization idiom doesn't exist. Scoped locks and smart pointers can't be implemented, so leaks of memory and other resources become much more likely.
More efficient! The C++ standard's support of type-based alias analysis allows C++ compilers to elide load and store instructions that compliant C compilers cannot. (A full discussion of type-based alias analysis is beyond the scope of this article, but see "Type-Based Alias Analysis" in Dr. Dobb's Journal.) Inlining allows C++ compilers to generate programs that make fewer function calls and therefore waste less time on function-call overhead. In combination with templates, functors allow C++ to easily generate efficient inlined custom code at compile time. This is why the C++ std::sort() will beat the perfomance of the C qsort() function every time. Finally, the C++ standard library containers, though much-criticized by developers, are likely to outperform equivalent code written by junior C coders. (For that matter, STL containers have outperformed the custom code they replaced in every case where I've seen data structures switched to use the STL.)
While I prefer C++ to C, I do agree that a C++ engine core works best in combination with gameplay code written in a language that supports a higher level of abstraction. That's not because C++ lacks programming constructs of more modern languages, though. I confess that I haven't worked with aspects or closures enough to even know whether they give great productivity improvements. I have done enough procedural programming to doubt that code written in a procedural language is going to be inherently more clean and elegant than code written in C++. And I'm generally skeptical of the benefits of garbage collection. Memory is one resource among many (file handles, critical section locks, D3D resource reference counts, etc.), and a resource management paradigm that only covers memory is of limited use. (If you think garbage collection "just works," read this explanation of the complexities inherent in C# finalization.)
No, the compelling advantage that a more abstract language has over C++ is faster iteration time. The efficiency and power of C++ make it the best language choice for core game engine technology, where runtime efficiency is important and iteration time is not. For gameplay code, however, those requirements are reversed. The ability to iterate quickly on gameplay and get immediate feedback on design changes is paramount. But gameplay logic executes infrequently compared to physics or graphics routines, so highly-optimized native-code inner loops are less important.
For rapid iteration, you want a simple language that compiles quickly or doesn't need to be compiled at all. You want to be able to make changes and see their effect immediately in-game, without having to restart, or even reload a level. You do not want the daunting features of C++ that make experienced programmers pause to think and make novice programmers write buggy code: pointers, a menagerie of different integer and floating-point types, multiple string representations and manual iteration over containers.
It's because of these considerations that Lua has become almost as popular in the game industry as C++--at least among those game companies that elect to use an off-the-shelf scripting language. Lua is lightweight, dynamically typed and interpreted. It's garbage collected, but as long as all your other resources are owned and managed by code and not script, garbage collection works just fine and saves you the headache of dealing with memory allocation.
So I hate to admit it, but I come down on the side of conventional wisdom. Write your game engine in C++. Write your gameplay in Lua. Those might not be the right answers for the next generation. I can imagine a language with better support for concurrency stealing the application domain from C++ sometime in the next decade. And C#/Mono is nipping at Lua's heels as a game scripting language already. But for now, I think the C++/Lua combination is as good as it gets.
Any opinions expressed herein are in no way representative of those of my employers.