The only game in town
Computers are like Old Testament gods; lots of rules and no mercy.
-- Joseph Campbell
Introduction
There has been a little bit of resurgence for functional languages, I myself have on occasion spoken for functional languages, as have Christer Ericson and several others. Functional languages just makes sense in a way that you really can't appreciate until you've dabbled a little bit with them. The elegance of it all takes my breath away. Algorithms fall naturally out, the syntax is usually clear, precise and to the point without any ADA or COBOL like verbosity. Traditionally the tools support for functional languages have, well sucked vacuum, which prevented anyone in their sane mind to actually use them for anything in real productions [1].
Now, one might point out that this and that functional language is better in some sense, but when it comes down to it, they simply don't have the right tools, or even the talent pool to hire from, to make them viable languages for games. So we continue on with C++. Which brings me to my next point, we use it since it's the only game in town. Really, it sucks, it's awful and the atrocities you can commit in this languages boggles my mind. But the fact is that all other alternatives suck even more and are less suitable for console games development. Sombody called C++ "the most tragic local maxima in the games industry", and it's sad but true. So while I continue to bitch and moan about the deficiencies of C++ I still use it. I still think it's the best thing available, but from a poor selection indeed. It's 2008 god damn it. Where is my personal jet pack? Where is all that computer nonsense that I saw on TV in the 80s? It's more complicated than ever to actually use a computer, and to program it you have to actively go and find the manuals. Complicated manuals. It's not like the good old days when I had my Commodore 64, which had an instant programming language at the tip of the power switch, a very simple processor (good old 6502) and an actual paper manual which showed me how to actually use and program the computer. Today you are lucky if you get a shiny pamphlet that shows how to connect all the cables and that is it! I mean, today's youth is completely taken aback that there is a built in help in windows (arguably that one is really bad and incomplete). Buying your kid a computer today and expecting them to learn how to program is not feasible. On the other hand one can today with a little bit of google fu dig up the most amazing things, so I guess it's a wash in the end. But I miss paper manuals.
The only game
But I digress, C++ was the game. The only game in town. The problem I have with C++ is that it's far too easy to mess up. You basically need to be fluent in the standard to be able to program any of the advanced features, like casting a pointer into another! No seriously, that is one of the most common errors I've seen. Every time you cast a value to another you need to ask yourself if it's legal. I promise that 99% of the cases it's not due to aliasing issues (again see paragraph 3.10.15 in the standard or read a more verbose explaination of strict aliasing by Mike Acton). And for the remaining 1%, consult the assembly before opening the champagne. As we are trying to make the compilers better at optimizing for speed, and as we move away from out of order instruction CPUs and in favor of in order (Xbox 360, PS3 and Larrabee) more of the burden for good efficient code it placed on the compiler. And they only way the compiler can make the code faster is to rely on promises of the language and then do safe transformation based on this. But if the programmer has broken the standard, i.e. not know that the heck the language is, then the compiler will most probably (if it's not Visual Studio, because it does very little in terms of these optimizations) produce garbage. Absolute garbage. And it will tell you nothing. Because you lied to it and it's sulking.
Now there are a couple of items in the language that you should try to stay away from since they usually cause really big headaches and require intimate understanding of the standard. Especially if they are intermixed, since most of the things are orthogonal from each other.
- Template meta programming. No. Don't even think about it. There are always better ways to do it. Less glamorous perhaps, but better. The problem with template meta programming is that it's very cool in a way that you are doing something that's fairly complicated, but not the way the language was designed for. The fact that it is considered clever is another red flag, almost invariably clever code will get you into trouble. Remember that you need to be twice as clever to debug the code as you were when you wrote it. And I can not even begin to read the error messages out of the compiler during template meta programming.
- Multiple inheritance. Always a bad idea if you're not forced by a third party vendor. And if that is the case, either convince them to change their APIs or switch vendor. This is absolute madness, since it requires nontrivial knowledge of the object model in C++. If you mix casts and virtual functions into the mix, then you are either on the standard committee or a clueless fool. Either way, you will wind up with code that will be extremely hard to debug and understand, and we all know that sooner or later you will have to debug this...
- More than one level of inheritance, or inheritance from non pure abstract base classes. This is just ill advised.
- Casting before reading 3.10.15 in the standard. And even after that you should cast pointers with extreme care. And never ever cast a float pointer into an int pointer. Never. Every time you do that, a cute little kitten dies. And on top of that, with most optimizing compilers you will run the risk of getting the wrong result due to register optimizations. Consider the following code:
Of course the problem with this is that you probably will never get the sign bit cleared since the compiler will generate the following code:
float abs(float a) { *((int*)&a) &= ~0x80000000; return a; }WTF? Since the compiler will pass the floating point value into the function in a float register, and then it sees that no float operations are done, and then return the result in the same float register, a good optimizing compiler will see that the integer operations can not possibly affect the float value (this is correct), realize that no reshuffling of registers is needed and insert the single return statement. Now compilers that doesn't do such a good analysis will probably (and I use the word probably) generate code that will clear the sign bit. In this case that is probably what the programmer intended (but incorrectly coded), but consider all the other cases where you actually want the compiler to be smart and generate the minimal and optimal set of assembler instructions to do what you told it to do. Actually, it's probably a whole blog post just to discuss the finer pointers of casting pointers.blr
I first learned C++ about 13 years ago and I still find new things about it that I simply didn't know. The standard is absolutely huge and it is very hard to keep up and learn all of it. My current strategy is to limit the parts of the language that I use to those that I'm comfortable with, as well as the runtime and the compiler (since traditionally the compilers that have been standards compliant have been very few. Microsoft Visual Studio has never been one of them, still isn't. And the vast majority of Visual Studio developers run in the most non-standard compliant mode, i.e. they don't turn on /Za). Now that part of the language is probably surprisingly small, although I perhaps feel that others overextend into areas where they certainly shouldn't be.
In closing
So is there no help? Well, there are a couple of books you should read up on. One of them is my all time favorite book, Expert C Programming: Deep C Secrets. It's really an excellent book that clears up such important concepts like why a pointer and an array is not the same, what interposing is (and why it's a pain in the ass during linking) sprinkled with anecdotes from the computer world that makes it a very good read in it's own. Don't let the fact that it's a C book stop you, the concepts in this book are the fundamentals of C++ as well. The second one is Inside the C++ object model, which you should just pick up if you are seriously contemplating doing anything that requires low level understanding of how the language works. Like figuring out where from the this pointer a certain variable is. In a safe manner. Third, if you are serious about programming in C++, you should pick up a copy of the Annotated C++ Reference Manual. It covers the old standard, but see it as a book that teaches you how to read a reference manual and then pick up a copy of the real standard (electronic or the paper version).
However, even armed with the knowledge contained in these books, proceed with extreme caution and choose your battles. There is a reason why there is a myriad of books out there for C++ that has traps, pitfalls and tricks in their titles.
Footnotes
[1] Although it you're really good, like Naughty Dog, you say screw it, I'll just write my own tools, including compiler. They did just this for GOAL, a lisp derivative to drive their PS2 games like Jak and Daxter. So it can be done, but a lot of effort is required. Apparently, Mario 64 was also written with some form of LISP.
C++ (and C#, Java, ADA, etc) are Simula based languages. I've been writing a bunch of Objective-C for the iPhone. It is actually quite nice. Objective-C is a dynamic language inspired by Smalltalk. There are a number of language features that combat Death by Design Patterns.
Design Patterns was a book about implementing in code a lot of features that were available in more dynamic languages like Smalltalk.
Random comments to your random rant.
Javascript is a functional langanuge is is EXTREMELY popular. Want to learn about how functional it is, read "Javascript: The Good Parts".
Yea, I know that's not going to replace C++ for console game programming but it is nice to know that a functional programming language is probably the single most popular language on the planet.
Otherwise, your rant seems like the rant of someone ranting against things he doesn't understand. Some people don't like the "condition ? value1 : value2" style because they are not familiar with it. Remember when you first started C and something like "printf ("found %05d files in %-5d folders\n", files, folders);" looked like complete gibberish? What is that weird % thing and that \n stuff? What does that semi-colon mean? Now you look at that and understand it without even thinking about it.
The point is, learn C++ and the parts that you find scary stop becoming scary and start being easy to read.
Meta Programming? My last project has some cool meta programming that devined function signatures for typesafe C++ to scripting language bindings. That meant from C++ you could expose a C++ function to the scripting language just by going
ExposeFunction(MyFunction);
And the meta programming magic would figure out what arguments that function needed and their types and generate the correct structures. That means much simpler code and NO chance for them to get out of sync that would plauge some manual way of doing it. As far as I can see that's a huge WIN, not a loss.
Things like boost::FOR_EACH also seem like a big win, simpler code, less chance for error but it required meta programming to make work.
Multiple Inheritence: At both Sega Japan and Sony Japan I saw several teams using it without incident and I've shipped a few that used it well to no ill effect. Clearly they were able to use it to their advantage. It's just another tool, use it where it makes sense, saves time and doesn't cause problems. Deciding not to use it even where it makes sense seems awefully limiting.
GOAL, having worked with GOAL my personal impression was not that it was its LISPness that made it cool. It was rather having their own compiler which let them:
*) Compile and Reload individual functions on the fly during development.
*) Make inline assembly a more first class feature of the compiler
*) Compile into assembly for speed (maybe other scripting languages do this?)
The point is, IMO, it wasn't the LISP that made it great, it was those other features. Take any language, write your own compiler and make it support those 3 features and you'll get all the benefits.
The LISPness has downsides as well.
*) Because you can re-write the compile-time language so easily every programmer writes tons of compile time LISP macros and then reading their code looks like a different language from every other programmers. It's a meta-programming nightmare.
*) There no are tools. Your editor doesn't do cross references or tags or refactoring or all the other fancy things modern IDLs do that help me get work done so much faster today. (maybe emacs does do all that stuff for lisp now)
*) Using any library is a chore. Sony comes out with a networking library? Oh boy, someone now has to write the lisp bindings for it before you can even write your wrapper library to use it. Yay to more work!
Hi gman,
Hehe, yes the post is fairly random. The joys of not having an editor! :)
The Javascript book (is it that one?) looks interesting. I always gloss over most web related technologies since I really find that stuff boring (hey, I try to make games), but I do get my fair share of those things at work (we have an integrated webserver in the game doing various crazy stuff).
"Otherwise, your rant seems like the rant of someone ranting against things he doesn't understand. "
Well, that's not really my point at all. My point is that if you need some 10+ years to learn the language, and I really do mean learn the language, I would rather stay with a subset of it. I'm sure that you can employ all the advanced features of the language successfully, but I think the return rate is diminishing pretty quickly, and also requiring people to have insane C++ skills. In reality the vast majority of the C++ programmers have not even mastered the basics as outlined in section 1,2 and 3 (basics) in the language standard. That's around 50 pages. The rest of the standard is almost 700 pages in addition. With that little understanding many programmers propose to go off and use 90% of the language. I would call that hubris, or just plain madness.
I would not say you need template meta programming for script bindings (unless you're doing something really clever). I made script bindings both for Lua and Python and they pretty much get by with regular templates, overloading and some old fashion repetition. I think you've misunderstood what template meta programming is.
Boost and STL for_each seems nice at first, but they quickly devolve into something that's much simpler in a plain old for loop. Now, they don't even parallelize it under the hood, so there is really no good reason to use them. It will however complicate reading the thing for the next person, unless you all are fluent in boost. Another point to make for STL's for_each is that it forces you to move one off code somewhere else. Instead of having the loop body, you need to move the code outside the function (since, remember that a template argument must have linkage and function scope types have no linkage, so a function scope functor can not be the template argument for for_each). It just makes the code harder to read. There is some merit in that you can now reuse the body, but an inline function will do as well for the reuse if you absolutely need it.
As for multiple inheritance, I don't doubt that you can make it work for you, I just question why. There is nothing that says that you absolutely need multiple inheritance to solve certain problems, most of the situations when you "need" it are of your own design. And for those who use it, I highly doubt that they've considered all the implications and the implications on memory layout, and casting between types. Even implicit casting. Or what vtable model their particular compiler employs since it now suddenly becomes very important if you happened to have virtual functions in any of the classes in the hierarchy. It certainly can be made to work, but I'm guessing that most people who do this just do it without really knowing what happens behind the scene, down to the details so that they can decipher the assembly and the memory dumps and tell you exactly what is what.
"The point is, learn C++ and the parts that you find scary stop becoming scary and start being easy to read."
I would disagree. The more I learn about this, the more scared I am. You can write some pretty fragile constructs in the language (take the spectacular failure of std::vector<bool> specialization, written by the standards committee no less, and these guys really should know the language!) that are susceptible to breakdowns. The fact that they don't necessarily manifest themselves in the current generation of compilers, doesn't really stop it from being scary. For example, on the current generation of consoles, we are paying for constructs that have been done, but always been been wrong (e.g. strict aliasing), but we got away with before without knowing it.
Finally, the LISP points. Yes, the first three points could probably be achieved in some simple script language (or even C with e.g. LCC), and much of the productivity gains could be won here. There is however a tendency to continue in the old thought circles, even when switching between languages. I find myself very much C++ centric when I've programmed in that for a while, and when I switch over to some Python programming (sometimes referred to as LISP Light) it takes a little time to realize that I don't have to solve problems quite the same way since I have a much more powerful language now to play with. I suspect one you overcome this barrier, a more powerful embedded language would become a win. But we also need to remember that most of us C++ programmers have spent a fair amount of years programming C++, and usually very little time on that other cool functional language. It's not fair to assume that we would hit the ground running and write really optimal code in a language whose idioms and power we barely know.
"The more I learn about this, the more scared I am."
I couldn't have said it better, this is exactly how I fell! A few years ago I was pretty confident in my C++ skills but nowadays, each line of C++ seems suspicious to me, as I try to imagine all the subtle horrors that may lurk in the code.
Oh, and you just ruined my day with this strict aliasing thingy! I'm going to check my code, I may have some cute little kittens to bury.
I think you're quite right. The best thing to do when we dealing with C++ is probably to avoid the most complex features as much as possible. Unfortunately, the most complex features are also the more attractive because, well, as you said, they make you feel really smart when you use them, and coders like to feel smart. Until they realise that feature X doesn't work *quite* the way they expected...
And by the way, yes, C++ does suck. This is sad, but what saddens me even more is that most C++ coders don't realize it and are happy dealing with this mess (heck, I used to). This makes it even harder to ditch C++ : how could a better tool replace it if people aren't even looking for one?
Ah, but GOAL wasn't a functional programming language.
GOAL was a curious beast - it had LISP's (lack of) syntax and C's imperative local-variable-based computation model. The first point made it easy to write powerful macros (for instance, the language's for-loop was implemented as a macro!) The second point made it easy to compile to machine code.
What GOAL didn't have was garbage collection, which meant that all the features we think of as LISPy (e.g. lists and closures) were actually not implemented.
Essentially, GOAL was a way to directly write a C AST and have it input directly "into the brain" of a code-generating compiler.