Neo-LISPer said:
Hey
Recently, I researched using C++ for game programming and here is what
I found:
While I personally prefer Lisp for my side projects, C++ is not an
absolutely horrid language for game development. It is definitely much
better than C; overloading operators and templates allow a "weak man's
macro system" and virtual functions provide some much-needed dynamicness.
C++ game developers spend a lot of their time debugging corrupted
memory. Few, if any, compilers offer completely safe modes.
Why must both sides of the garbage collection debate go to such extremes?
Pro-garbage collectors say that garbage collection prevents all resource
leaks. I know that this is not true as I have seen many resource
"leaks" occurring because the programmer forgot that some part of the
code would hold a reference to some data.
Anti-garbage collectors say that garbage collection is fast, but with
unreliable slowdowns. While this is true, I have found that in
application development, the slowdown is not noticeable at all. I view
arena allocation and deallocation as a more game-centric garbage
collector, which works quite well in many games.
On the whole, I find that I rarely need garbage collection in my
programs. But when I do need garbage collection, I *really* need it. I
think Lisp is right here to default to the more general solution
(garbage collection) unless you explicitly tell it otherwise.
Unsurprisingly, there is a very high failure rate among projects using
C++ for modern game development.
There is a very high failure rate among modern game development
projects, period. I don't think this has as much to do with the
programming language as it does with insane schedules/no real
direction/increased expectations. This is improving. Supposedly.
That doesn't mean that using a more high level language wouldn't speed
up development -- it would.
You can not even change function definitions while the program is
running and see the effects live (the ultimate debugging tool).
Alternatively, you can't execute a small portion of the program
without compiling and linking the whole thing, then bringing your game
into a specific state where your portion of the code is being executed.
A REPL debugging mechanism allowing function definitions is extremely
cool. If it had an integrated unit-test tester, that would be super
good. It's so good that I find myself implementing a half-assed REPL
test loop for any language which doesn't have a built in one. In C++ it
looks like this:
void functionToTest( int a, string& b );
int main() {
int a;
string b;
while( true ) {
BKPT();
functionToTest( a, b );
}
}
Every reasonable debugger will let me change the values of a and b.
It's a Read, Eval, Print Loop with out the Reading.
The static type system locks you into a certain design, and you can't
*test* new ideas, when they come to you, without redesigning your
whole class hierarchy.
I can't comment on this one way or the other, as I only have a few
months experience with Lisp. My guess is that the implicit interface
will prove similarly difficult to modify with Lisp as the static type
system does with C++.
Any eXtreme Programming Lispers or C++-ers care to comment?
C++ is so inflexible, even those who do use it for games, have to
write their game logic in some other language (usually very slow,
inexpressive and still garbage collected). They also have to interface
the two languages.
This is a very valid point. Game logic is one of the most obvious
applications of a REPL and function redefinition at runtime, which is
why many games use a language such as Python that supports such
features. As stated above, this is a great feature for *any* part of
development.
C++ lacks higher-order functions. Function objects emulate them
poorly, are slow and a pain to use. Additionally, C++ type system does
not work well with function objects.
Really? I find that function object emulate higher order functions
extremely well. It would be nice if the dispatch on them could be
either static (using templates and overloading) or dynamic (using
virtual functions), but it's simple to create such a library.
The main problem I have with function objects is that writing a separate
class is distributing logic in a way that's both confusing and annoying.
I should not have to write a separate class for this one function when
all the logic for that class is available to the compiler.
I already know about Boost.Lambda and I have found it to be impossible
to use. Every time I make a little change in my lambda function, I get
tons upon tons of errors. I last tried to use Boost.Lambda about a year
ago; has it improved its usability?
C++ programs can not "think" of new code at run-time, and plug that
new code into themselves in compiled form. Not easily, anyway.
I assume you are thinking about closures like cl-ppcre uses. While this
is doable in C++ using template meta-programming, it's so convoluted to
not be usable. C++ really needs a macro system like Lisp's.
C++ coding feels very repetitive, for example, when writing class
accessors, you often have to write const and non-const methods with
completely identical function bodies. Just look at STL.
A better macro system would help here again. I know that I'd much
rather write:
DEF_ACCESSOR( int foo(), { /* code */ } );
than
int foo_impl() const { /* code */ };
int foo() const { return foo_impl(); }
int foo() { return foo_impl(); }
The first is just more to the point.
When programming in C++ you feel like a blind person trying to draw
something. You don't _see_ the data structures that your procedures
will operate on. Lisp programming is much more visual.
I have no idea what you are saying here. Can you give an example of a
problem where the solution in Lisp is much more visual than in C++?
Constructors and smart pointers make it hard to tell cheap operations
from expensive ones.
Any language with flexible abstractions is going to have this problem.
Can you tell me if foo() is faster or slower to execute than bar(a,b)?
C++ lacks automatic memory management and so it encourages copying
objects around to make manual memory management manageable.
Reference-counting schemes are usually slower than modern garbage
collectors and also less general.
See above for my feelings on garbage collection.
Most important, C++ syntax is irregular, and you often find yourself
typing repetitive patterns again and again - a task easily automated
in languages with simpler syntax. There are even books on C++
patterns, and some C++ experts take pride in being able to execute
those patterns with computer-like precision - something a computer
should be doing to begin with.
I agree here as well. C++'s syntax is awful, and its deduction rules
are even worse. This made Boost.Lambda to be such a pain to use. A
good macro system would help here as well.
C++ programs are slow: even though the compilers are good at
micro-optimizing the code, programmers waste their time writing
repetitive patterns in C++ and debugging memory corruption instead of
looking for better algorithms that are far more important for speed
than silly micro-optimizations.
As stated before, macros would be nice. Unlike template meta
programming, I find that quasi quotation macros make sense.
It's hard to find good programmers for C++ projects, because most of
the good programmers graduated to languages like Lisp or avoided C++
altogether. C++ attracts unimaginative fellows with herd mentality.
For creative projects, you want to avoid them like a plague.
It seems to be quite easy to find good programmers for C++ projects.
It's hard to find great C++ programmers; the kind that think outside the
proverbial box. Lisp seems to encourage such thinking by its very
nature of being a programmable programming language. They're there, but
they are also much more rare.
If this is a good or bad thing is left to the company
It is my opinion that all of the above makes C++ a very bad choice for
commercial game development.
Since you stated many strengths of Lisp, it is natural that on most of
these statements, Lisp is better. On the whole, Lisp and C++ are very
similar (except with regards to macros), Lisp and Python/Java/C#/your
favorite dynamic language even more so. Lisp and C++ just take
different approaches to what the default assumption should be:
Lisp tries to assume the most general, and let the programmer specify
specifics when the generic solution is too slow. Lisp prematurely
pessimizes.
C++ tries to assume the fastest possible thing, and let the programmer
specify generics when needed. C++ prematurely optimizes.
So which is worse? Premature optimization is the root of all evil, but
premature pessimization is the leaf of no good. I prefer to start with
the general case and then optimize the hell out of it if needed, but
other programmers differ.
-- MJF