Static initializers will murder your family

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:

namespace {
static const std::string kSquirrel = "sad squirrel";
static const Superhero batman;
}
// or
class Foo {
  static const std::string panda_ = "also a sad panda";  
}

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:

namespace {
static Superhero batman;
static Superhero robin = batman.getSidekick();
}
  // In x.cpp:
  static Superhero batman;

  // In y.cpp:
  static Superhero robin(batman.getSidekick());
  // If that wasn't believable, imagine it was something like:
  // static Superhero robin(BestSuperhero::batman);
  // where BestSuperhero is a namespace or a static class and
  // you call batman.getSidekick() in robin's constructor.
  

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.

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.

// Both of these are ok, because 0 is a compile time constant, so it can't
// change. The const doesn't make a difference; it's the thing after
// the = sign that makes the difference.
static const int x = 0;
static int y = 0;

// Below, both the pointer and the chars in the string are const, so the
// compiler will treat this as a compile-time constant. So this is ok
// because both the thing before and after the = sign are constant.
static const char panda[] = "happy panda";

// This, however, calls a constructor, so it's not ok.
static const std::string sad_panda = "sad panda";

static int a = 0;  
// This is not ok, because the thing after the = sign isn't a const,
// so it can change before b is initialized.
static int b = a;  

// This has to call the Muppet() constructor, and who knows what that
// does, so it's definitely not a const, and a case of the static initializers.
static Muppet waldorf;

Them’s the breaks

There’s a couple of ways in which you can fix this, some better than others:

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:

namespace {
static const std::string bucket[] = {"apples", "pears", "meerkats"};
}

const std::string GetBucketThing(int i) {
  return bucket[i];
}

We can fix it by moving bucket into GetBucketThing():

std::string GetBucketThing(int i) {
  // Sure, it's a non-trivial constructor, but it will get called once,
  // the first time GetBucketThing() gets called, which will be at runtime
  // and therefore a-ok.
  static const std::string bucket[] = {"apples", "pears", "meerkats"};
  return bucket[i];
}

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.

puppy

« Presentation slides and writer's block Presenter notes that don't suck »