[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