[computer-go] Orego's Adventures in C++ Episode 2 posted
Eric Boesch
ericboesch at hotmail.com
Wed Aug 9 20:33:24 PDT 2006
Some C++ comments (self-indulgent, no go content, and probably old news to
experienced C++ programmers, so by all means ignore this email if you value
your free time) --
Depending on your point of view, there is no C/C++ standard bracketing
style, or there are many. I'd be happy if Java style bracketing (which is
also one of the competing C/C++ non-standards) took over the C/C++ world
just so there would be less pointless style variation. (Then, it would be
nice if C++ programmers could settle on when to use studlyCaps, QT style,
and when to use underscore_names, standards style -- I'm not a fan of that,
since it gives no hints to distinguish type names from variable names. Then,
somebody could wave a wand and negate the stupid idea that C++ header files
should violate file system conventions by, unlike virtually all files except
Unix executables, lacking an extension like .h or .cpp -- by the way, that
peculiarity confuses not just people, but also make's default compilation
rules, which can cause make to overwrite your C++ headers if you write them
that way.)
Pointers in general are OK; you can't generally say they are bad or good.
Including child structures directly in parent structures (instead of
including pointers to those structures) is often good if the parent really
exclusively owns the child -- it's always nice to avoid a memory allocation.
But when passing objects as parameters to functions and methods (the C++
idiom is "member functions" -- if only fewer OO programmers were addicted to
making up new names for old things, e.g. "static factory" for "object
cache"), it is usual, and usually faster, to use pointers or references.
Generally, if you don't need to make a new copy of an object, then use a
reference or pointer. Whether to use the pointer or the reference in a
parameter is mostly a matter of style, though in my opinion (and possibly
most other programmers'), if you intend to modify an object that is a
parameter, you should usually use a pointer parameter instead of a reference
parameter, because invoking a method as a.foo(&b) makes it clear that b
might be modified, whereas writing a.foo(b) does not.
One vaguely Java-ish idiom that really should be avoided like the plague is
this:
Terrible:
{
Foo *a = new Foo(initialization parameters);
...code goes here...
delete a;
}
Normal:
{
Foo a(initialization parameters);
...code goes here...
}
new is never implicit. If you skip the new, then in most OSes, you've
skipped the memory allocation -- the object goes onto the stack instead --
and if you can get away with that, you have saved a lot of time. So the
normal version saves a memory allocation and deallocation, and it's simpler,
and there's no danger of forgetting to delete the object when you're
finished because it's deleted automatically as soon as it goes out of scope
(not just at some indefinite moment in the future), and the "code goes here"
part might or might not be slightly faster in the "normal" version too,
because there is some opportunity for additional optimizations (but
manipulating pointers to objects is usually just as fast as manipulating
objects themselves, because the "object itself" is usually addressed via a
hidden pointer anyhow), which all explains why the terrible version really
is terrible.
My code has loads of vectors, but few calls to new -- only in cases where
the memory dynamic is complicated (e.g. objects A and B together own object
C, which is to be destroyed only after both A and B have been destroyed),
and in those cases I use new with TR1 shared pointers (which are not C++
standard, but in a few years they probably will be) so I don't have to worry
about calling delete later. Fifteen years ago, C++ memory management was
harder, but C++ has gotten better (and more complicated) since then.
A key point is that unlike Java ArrayList<Integer> or Stack<Integer>, which
are (especially because of the cost of turning ints into Integers) terrible
time sinks that no expert Java programmer would use in a speed-critical
piece of code, a C++ vector<int> is very efficient. It seems to me that
dynamically allocated (using new) arrays do not perform much better than
vectors of the same size (although I admit this assertion could use more
testing, and in any case it depends on having a modern compiler that
optimizes well).
Incidentally, GDB support for C++ has in the past half-decade or so improved
from "lousy" to "so-so". It's far better than nothing, but well short of
perfect. (That's just a warning, not a criticism of the GDB contributors,
who unlike me are actually doing something about the problem.)
Finally, method definitions that appear within the class definition have an
implicit "inline", so you don't need to add that yourself. The compiler ends
up deciding what to inline and what not to anyhow -- the one fixed rule is
that if a function is defined in one .cpp file, then it can't be inlined
when called from another .cpp file -- so put the function definition in the
class definition (and the .h file) instead. Also, it's often impractical to
inline virtual functions, since the program usually doesn't know which
function body to actually call until it looks the right one up in the
virtual table.
More information about the computer-go
mailing list