But only if your family is code.
So this is a bit of a terrible blog post because a) it’s about a really obscure atrocity that happens in C++ (as opposed to the common atrocities that happen in C++ on the regs) and b) there are not enough funnies in the world to make up for it. I recommend skipping it if you’ve just eaten, are feeling light-headed, or don’t want to make eye contact with C++. As a general policy, you should probably never make eye contact with C++. It can smell fear.
Programmer, meet static initializers
We’re going to be talking about static class objects, or objects defined in a global/unnamed namespace, such as these fellas:
Static initialization is the dance we do when creating these objects. This is not a dance we do when we initialize things with constant data (like static int x = 42
); the compiler sees that the thing after the =
is constant and can’t change, so it can inline it. However, if you try to initialize a variable by running code (e.g. static int x = foo()
), then this is not a constant anymore, and it will result in a static initializer. In C++11, I think constexpr
will let you hint to the compiler that the thing after the equal is a constant expression, if it is that, so it can compute it at compile-time. I don’t get to use a lot of C++11, so this is still about nightmares of C++ past, and I don’t think constexpr
will do away with all of the murders anyway. Finally, the compiler promises you to run all the static initializers before the body of main()
is executed. That, unfortunately, doesn’t mean much.
Why static initializers are bad news bears
As Douglas Adams, the inventor of C++ said, static initializers have “made a lot of people very angry and been widely regarded as a bad move”. Apart from being hard to spell, they tend to throw up on your shoes:
- Static variables in the same compilation unit (or the same file) will be constructed in the order they are defined. This means that this code is predictable, and always does exactly what you think it does. This is also the last of the good news:
- Static variables in different translation units are constructed in an undefined order. This is so terrible it has its own name: the static initialization order fiasco. It goes like this:
Yup. That’s it. Whether x.cpp
or y.cpp
gets compiled first is not defined (because C++), which means if y.cpp
gets compiled first, batman
hasn’t been constructed. You know what happens when you call getSidekick()
on an uninitialized object? Regrets happen.
- We’re not done yet. Why have insanely terrible code when you can have insanely terrible EXPENSIVE code! Evan Martin has a really, really good post about this, but the tl;dr is that because the static initializers need to happen before
main()
, that code needs to be paged, which leads to disk seeks, which leads to awful startup performance. Seriously, read Evan’s post because it’s amazing.
Spotting static initializers in the wild: an incomplete manual
Here are some examples of things that are and aren’t static initializers, so that at least we know what we’re looking for before we try to fix them.
Them’s the breaks
There’s a couple of ways in which you can fix this, some better than others:
- The best static initializer is no static initializer, so try const-ing all your things away. This will take you as far as defining an array of strings, for which you can’t pray the initializer away. (Trivia: Praying The Const Away™ is what I call a
const_cast
) - Place all your globals in the same compilation unit (i.e. a massive
constants.cpp
file). You can certainly try this, but if your project is the giant Snuffleupagus that Chrome is, you might be laughed at - Place the static globals inside the function that needs them (or, if they’re the village bicycle, make a getter for them), and define them as function-static variables. Then you know they will be initialized only once, the first time that function is called. Whenever it is called
That last bullet sounds like black magic, so here’s an example. This is the static initializer that we are trying to fix. Convince yourself that this code is no good:
We can fix it by moving bucket
into GetBucketThing()
:
Yup. That’s pretty much it. If you want more reading on the topic, here’s a neat chromium-dev thread discussing this in more details (and talking about when these static globals are actually cleaned up).
Mmmmkay.
I don’t know why you’ve made it this far. Maybe you thought there was going to be a joke or a prize at the end. There isn’t. There’s just this gif, and you could’ve just scrolled down for it.