Writing

Software, technology, sysadmin war stories, and more. Feed
Saturday, December 3, 2011

I hate spooky C++ action at a distance

I hate "spooky action at a distance" in code.

Quick, what does this print?

#include <stdio.h>
 
class Evil {
 public:
  Evil();
  ~Evil();
};
 
Evil::Evil() {
  printf("cat\n");
}
 
Evil::~Evil() {
  printf("bird\n");
}
 
static Evil evil;
 
int main(int argc, char** argv) {
  printf("dog\n");
  return 0;
}

Maybe you think that's simple, and okay, it is when it's all in one file like this contrived example. But I want you to consider this: what do you suppose happens when it's spread out across a bunch of files?

Where I saw this sort of thing in real life, one file defined the class and another one had the code in it along with the static definition which summoned it into existence. Then it went and did some non-trivial work in its constructor leading to some strange things going on which had no visible connection to the code I was running from a third file.

It looked more like this:

--- evil.h:
 
class Evil {
 public:
  Evil();
  ~Evil();
};
 
--- evil.cc:
 
#include "evil.h"
#include <stdio.h>
 
Evil::Evil() {
  printf("cat\n");
}
 
Evil::~Evil() {
  printf("bird\n");
}
 
static Evil evil;
 
--- main.cc:
 
#include <stdio.h>
 
int main(int argc, char** argv) {
  printf("dog\n");
  return 0;
}

Got that? There's no reference to it anywhere in main.cc. However, the way the Makefile equivalent was rigged up, evil.h and evil.cc were compiled into an object which then got linked into this mess, and that's enough to get it running.

Look what happens:

$ g++ -c evil.cc
$ g++ -c main.cc
$ g++ -o prog main.o evil.o
$ ./prog
cat
dog
bird
$ 

Got that? Yes, this is how C++ works. But just because it happens to do that doesn't mean you should make use of it!

Some sick, sad programmer somewhere dropped a few bombs like this on a project I had to work on not too long ago. The stench of evil from that code is forever etched in my mind.