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:
- Your code base was written before the invention of loose coupling. It becomes hard to write tests in a plain GoogleTest environment - simply because it doesn't add up to your environment.
- You are doing Windows programming. You bring in components that require a runtime that just isn't there in your plain ConsoleApplication.
- Your compiler settings tend to disagree with those of Google
- You want to test code in DLLs.
- You want to test code in Services.
- ...
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:
- We are going to write test classes in separate C++ modules
- We want a test class to be automatically become available in the test runner. No scripting necessary, no extra includes, nothing.
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...
- If a module is stripped out by the linker, its global variables are not called. Now, I tested this only for the Visual Studio 2010 and 2013 compilers, but
in my experience the following rules of thumb apply:
- Modules in applications are never stripped out. Even if you define your class as static, your linker will still call the global constructor for it.
- Modules in static libraries are stripped out if they are not used. (This makes sense, that is what you use static libraries for, after all). OTOH, you are doing testing, and if you don't use a function you don't really need its test included, do you?
- You add this functionality to a standard /SUBSYSTEM:WINDOWS type application - and suddenly some tests start disappearing. What happened? Likely you have a system exception - for example, an access violation - in your test code. If you do, then the WinMain app will fail silently (on my Windows 8.1 box it does, at least). It does write an exception to the Windows Applications Event Journal - but that is it, the app dies a silent death. There is an easy fix: just ensure your calls to the test code are wrapped in Win32 SEH.