Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Saturday, October 3, 2009

Everything Takes Longer In C++

So I had tonsil surgery last Monday. Even though I didn't feel up to programming for my day job, I couldn't stay away from trying to work on the MMO game project called Emergence that Ben and I are creating. When it seems like progress is going glacially slow on our game Ben and I always say "if only we had more free time to work on this..." Well I've had time this week - and I still made only baby steps in progress on it. Granted I was resting from surgery and on pain medicine, but still... I'm beginning to think it's not (just) that we have no free time. It's that with programming you put in enormous effort yet make only incremental progress.

If it takes a certain amount of effort to write software of a given complexity, writing software that's twice as complex doesn't take twice as long, it takes, I don't know, ten times as long, twenty times as long. The effort increases exponentially. Invert that and you get an equation that says that you only get logarithmic increases in output for every increase in effort. If you don't know what a logarithmic curve looks like, it has a knee bend. If you stay to the left side of the curve you get pretty good output for input, but as you move right it levels off quickly. To the right side of the curve, increasingly large increases in input result in increasingly small changes in output.

So I think I've been working dangerously close to right side of the curve lately, where what you can accomplish tapers off faster than the increase in effort. I've been getting better with some of the new programming techniques I'm using, and I've streamlined my build process and improved my debugging techniques. But here's the funny thing - all those improvements really just help me increase my work input. They don't directly increase the output. You see if I streamline my build process and my debugging techniques so that I can make a change and test it in half the time, that just means I can test twice as many code updates in the same amount of time. But if I'm on the inefficient side of a logarithmic curve, I may actually need to make four, or ten, or twenty times as many code changes at a time to actually double my output.

So that's what's so frustrating about this work. You run faster and faster but never seem to get anywhere. It's not hopeless though. I think the fact that the ratio of accomplishments from effort is logarithmic is some kind of fundamental law of software, but it is possible to tweak the shape of the curve. Although it will always eventually taper off, it doesn't need to taper so as quickly. I think changing the programming language you program in does the most to change this curve. C++ is very powerful and generates efficient programs, but it has a very sharp bend in its complexity curve. This is because doing anything substantial in C++ just involves so many fiddly little details, and the language does so little for you automatically. I'm not saying you can't build large systems in C++, but I am saying it probably took exponentially more work to do it than it would have to do it in a scripting language. And I'm not saying you shouldn't use C++; in order to get good performance in our game, we need to. But we're going to use both C++ and script language in our game, and I need to get out of the C++ part and into the scripting as soon as possible.

The ironic thing is that the component which is giving me all this trouble, where I'm working too far on the right side of the complexity curve, is the C++ component that will bind our C++ side of our game to our scripting side. In order to leave C++ behind I need to first overcome a trial by fire of the toughest C++ I've ever worked on.

Sunday, August 24, 2008

C++ Iterators

A friend of mine and I are collaborating on a game in the C++ programming language. The part I'm writing is a general purpose engine that will be used on both the game server and the game client, so I decided to use templates to allow basic game objects to be replaced with entirely different classes in the client and server, yet still use all the same code on top of them.

In the process of moving my code from regular classes to template classes, I uncovered a fundamental language wart or compiler flaw. It has to do with a problem I ran into using STL iterators. Before I go into what the problem is, let me explain iterators a bit.

The people who wrote C++'s standard template library want to break you of the habit of writing things like:

for (int i=0; i < vec.size(); i++)

Instead you should write this:

for (vector< int >::iterator i=vec.begin(); i != vec.end(); i++)

Do you see the improvement? No? It's supposed to simplify code for traversing containers--except that it's twice as much typing! OK, well it also gives your code portability to use different containers besides vector ...theoretically... except that we have to specify "vector" directly in the for loop to say what kind of iterator it is! Oops.

The real motivation behind iterators is that it unifies the concepts for the people who write the container classes. I won't say the users of the container classes can't use iterators in ways that also provide benefit, but the users also bear a burden of extra boilerplate that adds as much work as it saves. This is the sort of thing Bjarne Stroustroup means when he talks about the compiler vendors and library/tools group being overrepresented on the C++ standards committee compared to actual users of C++. At least we users are finally going to get the auto keyword in C++0x, which will allow us to just write the word "auto" in place of the vector::iterator gobbledygook.

But being the good little C++ citizen that I am, I've taken the trouble to use iterators in all my code, even if it is a lot more typing. That's how when I moved my engine definition into templates, I got burned badly.

Where before I had this:

for (vector< GameObj >::iterator i=vec.begin(); i != vec.end(); i++)

I now have:

template < typename T >
... ...
for (vector< T >::iterator i=vec.begin(); i != vec.end(); i++)

What is supposed to happen is that when I instantiate the code with T=GameObj or T=OtherGameObj, the code will get built at the point I instantiated it and the result of the latter will look like the former except with either GameObj or OtherGameObj substituted for T.

Instead, it turns out that you can't (at least on my compiler) use a template argument like T to specify the type argument to a container iterator. You can declare the container using that T, but you cannot access the iterator of the container you just created! On some containers like vector you can just go back to the old notation, but for containers like map<>, iterators are the only way to loop over the container. This is a Really F*ing Big Problem. This is like laying the foundation for a building and realizing you didn't leave room in the floor plan for any doors. The irony is that iterators from the standard template library don't work with template arguments! This is like forgetting to leave room for doors in the plans for a f*ing door factory!