Remember how annoying it was to try to get anything done in C++? Typing reams
of boilerplate and fighting mysterious compiler errors that need to be fed
through c++filt
to make any sense of them? Or maybe those days have faded
into the rose-tinged past, when we were “real” programmers, who ran through
the gauntlet just to prove we could.
It’s been over five years since I did any serious C++ coding, but I dusted it off for a pet project recently, and had forgotten a lot more than I thought. It was also a lot worse than I remembered.
The main thing I remembered is that C++ is more verbose (by line count) than modern languages. At a previous job, we translated a few servers from C++ to Java, and calculated that Java required about half as many lines of code, not counting custom libraries for things like network I/O (which you get for free in Java). I strongly believe that verbosity and boilerplate are bad features in a programming language, but I’ll save that argument for a future blog post.
But the quirkiness of C++ goes a lot deeper than its wordiness. Check out these things I’ve been uncovering as I work on my project.
Private inheritance
Woe be unto you if you think of the java line
class Complex extends Number
and you write it in C++ as
class Complex : Number
because gotcha! C++ predates consensus ideas about public/private exposure, and the default inheritance actually changes the exposure of all inherited members to be private (hidden). Like most of the 137,000 features of C++, this is probably useful to someone somewhere, and everyone else just has to remember the rule that you need to type
class Complex : public Number
to get standard inheritance.
Implicit constructors
This is a feature where, if you have a constructor taking a single parameter, like
CatList(int maxSize);
and a function that takes an object of that type, like
void feed(CatList& cats);
then you can make a seemingly nonsensical or broken call like
feed(3);
and it will compile. Why? Because the C++ compiler sees that you didn’t pass
a CatList
, and it starts rooting through your garbage to find something
that will work. (Like perl, C++ would rather do the wrong thing than fail to
compile.) So it finds the CatList
constructor that takes an int, and turns
your call into
feed(CatList(3));
so that it creates an empty list of cats and feeds them. I’m sure that’s what the author meant!
This became such a huge problem that C++ eventually added the explicit
keyword, so you can mark which constructors should be safe – more
boilerplate to memorize.
Scala added a similar feature, but reversed the sense, so you have to mark a
method as implicit
before it will be considered. You also have to import any
implicit conversions you want to use in a file, so they become a bit more
explicit. But scala coders will tell you that even with all these
restrictions, they can bite you from time to time. C++ making them default
behavior is just punitive.
Generic classes must live in headers
This is pretty obscure, but if you make a generic class, which are called “template classes” in C++, it must live entirely in a header file, or else be used only in the file it’s defined in.
Normal C++ classes have a header file with the class’s interface, and a code file for the implementation. This can’t be done for generic classes because the compiler still treats templates as macros: each possible variant of a generic class is effectively compiled into a separate class. If you put part of the implementation into a C++ file, the compiler can’t find it. You’ll just get context-free link errors.
There’s a great description of what’s going on here.
My favorite part is that the C++ standards body tried out a fix for this, but the fix turned out to be broken in a different way, so they deprecated the fix. (Details here.) It’s as if they’ve thrown up their hands and said “Well this is hopeless. Just remember to treat these classes specially.”
Method pointers
It’s possible to grab a pointer to a method in C++. Most people I’ve mentioned this to looked surprised, but really, it’s an old, supported language feature. If you have a class like
class CatList {
void add(Cat& cat);
}
then you can define a type for the add
method (which you should absolutely
do, because good grief look at this mess) – let’s call it Method
:
typedef void (CatList::*Method)(Cat& cat);
and you can pick out the method pointer and save it!
Method method = &CatList::add;
And you can call it later!
CatList *c = ...;
(c->*method)(cat);
If you’ve never seen ::*
or ->*
in C++ code before, that’s because they
are special operators added just to make this feature work. The parenthesis
around c->*method
are because they thought it would be funny to give these
new operators incredibly low precedence, for maximum surprise-factor.
I wanted to use this feature to translate method calls on a javascript (v8)
object into method calls on a C++ object. v8 will pass a (void *)
to the C++
handler, so all I need to do is stuff the method pointer in there and cast it
back on the way out… right? Wrong!
error: cannot cast from type 'Method' (aka 'void (CatList::*)(Cat &)') to pointer type 'void *'
The pages about this in the C++ faq read like “Get off my lawn!” and don’t explain why this won’t work, so I’ll try. In C++, a method pointer isn’t just an address; it’s potentially a struct containing the method’s address and some other data too. So the struct is too big to be assigned into a pointer.
When I mention this to friends over beers, most people guess that it’s because
C++ needs to store two pointers: one for the address of the function, and one
for the “this” pointer. But nope, it’s not that. You need to pass the “this”
pointer on the left side of the ->*
operation. In fact, nobody seems to know
what else a C++ compiler might want to store besides the function address. My
guess is that it might also want to store the address of the “vtable” – the
class definition/metadata.
If you’ve been feeling nostalgic about the old days of “real coding” in C++, maybe now you realize that you haven’t really been missing it. Sometimes the present really is better than the past!
(Photo credits: my dad’s painting, and a wikipedia arctile about the dust bowl.)