Part 4 of “Building World-class Art Tools” from Develop 08
This post boils down to one simple headline: don’t use C++ for game tools development. But nothing is ever quite that simple – so if you have time, read on 😉
That headline is deliberately provocative, but all I really want you to do is think. Think about your choice of language carefully. “Don’t use C++ blindly” might be a more accurate version of what I want to say. Since nearly all game development is done in C++, it’s too easy to use it for everything you do without thinking. Maybe it’s your favourite language, maybe it’s your best, maybe you’ve invested a lot of time learning it, and sometimes it’s the only language you know deeply. But is it the best tool for the job? Have you considered the alternatives carefully?
Here are some of the main reasons for C++ being a good choice for game development:
- It’s as close as you can get to the hardware without using assembler, so provides the best combination of efficiency and portability (alongside C). Compared to C, it has better abstraction and high-level programming facilities, an important consideration given the size of modern game codebases.
- It gives full, precise control over memory allocation and access – particularly essential on consoles with their limited memory (and it’s a hard limit – no virtual memory there).
- Decent implementations are available for all game development platforms, all reasonably consistent to the language standard (at last!). Not only are they available, they are usually the only real choice if you want a development environment and tools such as debuggers.
Given these reasons, I’m not going to disagree (not today, at least!) with using C++ for your game. But look at those reasons carefully: how many apply to your tools? Your tools almost certainly run exclusively on PCs with bags of memory, and don’t have anywhere near the same efficiency concerns as your game.
I spent my first 3 years in the industry devoted to learning C++ in depth. It’s a massive language (about to get bigger with C++0x!), with complexity and hidden depths. Mastering it makes you feel smart and confident, but can leave you heavily invested in the language and quite blinkered to all the other languages out there. If you’re in that position, I suggest you broaden your horizons:
- There are over 2500 documented programming languages! O’Reilly has a fascinating family tree of 50 of the most significant ones. While you’d be a crackpot to choose many of those, there are at least 5 to 10 options worth serious consideration – off the top of my head: C, C++, C#, Java, Python, Ruby and Lisp (even an option for games – don’t laugh – check out GOAL if you haven’t already).
- Your language is a programming productivity tool: while all languages are more or less equivalent in what they can say to the computer, they are quite different in what they say to people. I shouldn’t go on about this too much – you absolutely owe it to yourself to read Paul Graham’s essay, Beat the Averages. Whether or not you agree with the conclusion (“use Lisp”), you should understand the principle that languages differ in power. You already know this – would you write your tool in assembler? You know C++ is more productive than assembler, so why shouldn’t there be more productive languages still?
- Even if you don’t choose another language for production use, learning it will help you become a better programmer. Sufficiently different languages teach you different ways of thinking about and solving problems. One of Peter Norvig’s key pieces of advice is to learn at least half a dozen sufficiently different languages.
Once you’ve had a look around at the other languages out there, it’s time to weigh them up against C++.
Here are just a few concrete advantages of switching to a better language: reasons to stop using C++. You could get all these by switching to any of Java, C#, Python or Ruby:
- Automatic bounds checking, guaranteed prevention of bad casts, and lack of raw pointers. What these have in common is that they convert nasty bugs, where you’re corrupting some piece of memory by accident, into nice bugs. Nasty bugs are nasty because the symptoms are completely unrelated to the causes, and quite often happen in a non-reproducible or apparently non-deterministic manner: these are the bugs you spend ages chasing down. Nice bugs are those where you get an exception at the very source of the bug. Symptom and cause are one.
- More standard library functionality. The C++ standard libraries are totally threadbare. They’re addressing this in C++0x, but that’s a long time to wait to get basic stuff like threading support. Sure, there are many excellent things you can get in the meantime (especially from boost), but being non-standard is a disadvantage: it prevents code from talking to other code when they’ve chosen different libraries to do the same job. Plus, developers have to learn multiple libraries.
- A standard library that’s more useful. The worst thing about C++’s libraries are the incompatibility you get when you use a binary compiled against a different standard library implementation. We had this with middleware once, where they’d used std::string and we couldn’t even pass our strings into their API 😦 Although we worked around it, it’s pretty pathetic that the standard string class can’t be used reliably in a public API (except when giving out the library source).
- Reflection: the ability to poke around at runtime and get rich information about the state of your running programme. In an editor, let’s say an artist selects an object and you want to display a panel of its properties for them to tweak. In C++, you can do this, but you have to go to some lengths. Examples I’ve seen include littering header files with macros wrapping your data members (Renderware did this) so a preprocessing tool can generate reflection data at compile-time, or storing your object’s members in an associative container (forcing your source code to be inefficient and ugly). With reflection, it’s natural and trivial.
- Automatic memory management. This doesn’t free you entirely from worrying about memory; it is still possible to leak, but it definitely reduces the extent to which you worry about memory, and the amount of boilerplate code devoted to it. It’s particularly a big win for library writers; C++ libraries typically have to go to some length to explain who owns memory for objects being passed in and out of their API. It also opens up some sophisticated techniques with immutable data structures that share state. So sure, you can use boost’s smart pointers in C++, and garbage collection is coming in 0x, but the former is non-standard, and the latter is late to the party.
There are a couple more things I didn’t have time to mention during the talk itself:
- Compile and link times: when I go back to C++, I can hardly believe how much time is spent sitting and waiting for code to build; you become blind to it when that’s all you use. The core physical header-and-source-file mechanism is probably the most harmful part of the C compatibility legacy. The quicker you can build, the more iteratively you can write code. The power of iterating should not be under-estimated 😉
- Better development tools. Despite being an incredibly popular language, C++ has awful development tools (such as refactoring, code analysis etc), and that’s essentially because it’s so damn hard to parse. It’s essentially impossible to find a parser for C++: I know because I spent a long time trying, when I was interested in doing some analysis on our source code. Here’s what I found: gcc is free and obviously well capable of parsing the language, but you can’t point at “the parser”: parsing is massively entwined with the guts of the compiler, with no easy way to reuse yourself; EDG will sell you one for between $40K and $250K (!!); Elsa was a nice project but it’s telling that they gave up before actually finishing (“we can parse everything except the standard library headers” has to make you chuckle!!). Check out this page for more in-depth analysis – the author actually gave up and switched language.
A common pattern is that you can technically do all these things in C++ (all languages being ultimately equivalent in the Turing sense, and most other language runtimes being implemented in C++ underneath). But this is a false argument, because you essentially have to implement these features yourself! It’s a productivity issue – choose a language which already has them. It reminds me of Greenspun’s Tenth Rule of Programming:
Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified bug-ridden slow implementation of half of Common Lisp.
In other words, complex systems in low-level languages inevitably end up reinventing and reimplementing the facilities built into higher-level languages, and making a mess of it.
Plenty of smart people have written about the problems of C++. Steve Yegge is one of my favourites as he does it in such an entertaining way:
I’m not ready to bash on C++ yet. All of this stuff I’m writing, in blogs and the ADJ, is just practice for when I start bashing C++ in earnest, and trust me: when I’m finished, one of us (me or C++) won’t be here at Amazon anymore.
In the last few years, C# has become an excellent option for game tools. A few reasons in favour:
- It has all the main features you’d expect of a modern language. It started out pretty much like Java (avoiding some of the mistakes Java made), but they’ve moved a little faster in adding new features to the language recently.
- It’s easy to move from C++, on several levels: the language syntax is similar, if you use Visual Studio already, the development environment feels the same (better, in fact!) – and it’s simple to call your existing C++ code, so you don’t have to rewrite everything at once.
- It is highly efficient, so you won’t miss the speed of C++ much, if at all.
- Microsoft are clearly investing in C# for the game development ecosystem – just look at XNA.
- If you’re worried about being locked in with Microsoft, check out Mono.
- My team are using it and enjoying it: it must be true!!
Don’t be an extremist
Choice of programming language is clearly a complex issue, leading to “religious wars”. In such cases, there are always extreme positions being taken. Don’t ignore the extremists – I think it’s important and valuable to understand what they have to say (at least in issues like this, if not in general) – but never forget that in a complex issue like this, there is no one right answer, and positioning yourself at the extreme is often not wise.
This applies firstly to all the C++ bashing articles, including the ones I’ve linked above. To some extent, people love to hate C++ because it’s been used so widely (unpopular languages aren’t going to attract that kind of hatemail), and for big systems (these tend to stink somewhat and it can be easy just to point the finger at C++). It reminds me of Microsoft-bashing in that sense. C++ definitely still has its place: apart from anything else, when you programme in another language, you’re probably relying on heaps of C++ code beneath you in the language runtime, the operating system and so on.
I wrote this post because as game developers, we’re more likely to be at the other extreme. We tend to love C++ too much and not question it. If you think C++ is the best thing ever, the best tool for every job, you’re an extremist. If you’re there without realising it, open your eyes, try some other languages, and think for yourself 😉