Self-registration for global objects in C++

This page shows you how to create self-registering global objects in C++.

Motivating example: Unit Testing as an afterthought

Let's say you are working with a really large legacy code base of thousands of sources, spread through several hundreds of DLLs and EXEs, supporting multiple operating systems. And after all those years, you want to start adding unit testing to it bit by bit. What should you do? One day I had to solve that problem, and here is a writeup that hopefully includes a couple of hints that may help others to not repeat the same mistakes I did...

Well, the first thought that comes to mind is obviously GoogleTest. However, once you start thinking about it you may run into a couple of issues:

Long story short, you decide to become a non-traditional Entrepeneur and redesign the wheel.

OK, so you start writing your unit test classes. Soon you realize that your code includes hundreds of separate modules, so you'll end up with hundreds of test classes. But OK, this is what you want, right?

But actually, you do not only write N test classes, you also need to write code that calls these N test classes. This being C++, you'd need to include N headers, and then add calls to N objects, and suddenly it is quite a bit of work. Wouldn't it be nice if you could automate all that including-and-calling stuff?

So this is our task:

So let's start by writing a very simple base class for tests:

#ifndef reinventing_the_unit_testing_wheel_h
#define reinventing_the_unit_testing_wheel_h

class UnitTest
{
public:
    UnitTest(const char* name)
        : m_name(name)
    {
    }
    
    // abstract function, to enforce unit test implementation by derived classes
    virtual void PerformTest() = 0;
    
private:
    std::string m_name;
};

#endif // reinventing_the_unit_testing_wheel_h

Nothing challenging so far. Let's go to module foo.cpp and start writing our first test:

...
class MyUnitTestForFooCpp : public UnitTest
{
public:
    MyUnitTestForFooCpp() : UnitTest(__FILE__) {}
    
    virtual void PerformTest()
    {
        .. // do magic testing stuff here
    }
};
...
MyUnitTestForFooCpp myUnitTestForFooCpp;

How do you call this class? Right now, the only way is to explicitly call each class, like this:

...
#include "Header that includes definition for MyUnitTestForFooCpp"
...
extern MyUnitTestForFooCpp myUnitTestForFooCpp;
...
myUnitTestForFooCpp.PerformTest();
...

Ok, so but now imagine you have not one, or ten, but one hundred, or one thousand of those. Typing all those classes over and over again becomes rather tedious. So, how could you automate that? With self-automation. Ideally, you'd want something like the following:

#ifndef reinventing_the_unit_testing_wheel_h
#define reinventing_the_unit_testing_wheel_h

class UnitTest;
void RegisterUnitTest(UnitTest* ut);

class UnitTest
{
public:
    UnitTest(const char* name)
        : m_name(name)
    {
        RegisterUnitTest(this);
    }
    
    // abstract function, to enforce unit test implementation by derived classes
    virtual void PerformTest() = 0;
    
private:
    std::string m_name;
};

#endif // reinventing_the_unit_testing_wheel_h

So, what does the implementation of RegisterUnitTest look like? First attempt:

std::vector<UnitTest*> tests;
void RegisterUnitTest(UnitTest* ut)
{
    tests.push_back(ut);
}

There is a slight problem with this: you are using one global variable to register another one. However, there is no guarantee of the order your global variables are initialized. Well, some compilers document that the order is alphabetic, but that doesn't really help you. You could come up with a name that hopefully will be the last in the list, like zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz, but really there would be no guarantee.

However, there is one guarantee you can use: scalar values are initialized to zero. (Actually, this happens implicitly because the compiler will place the variable in a segment that the OS loader intializes with zero, but we are loosing focus):

std::vector<UnitTest*>* tests* = nullptr
void RegisterUnitTest(UnitTest* ut)
{
    if( !tests ) tests = new std::vector<UnitTest*>();
    tests->push_back(ut);
}

That gives you a pointer array that later in your code you can use to run all your tests, just by means of polymorphism and abstract base classes. Nice.

Some bumps in the road

When you start doing this, you may run into a couple of issues...