C++ quirks, remembered

09 Oct 2012

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.)

« Back to article list

Please do not post this article to Hacker News.

Permission to scrape this site or any of its content, for any purpose, is denied, regardless of your personal beliefs or desire to design a novel opt-out method.