Documentation Buy Contact Blog
Blog

Nifty Counters

Adam C. Clifton
5 Aug 2014

What is this Nifty Counter of which you speak?

Nifty Counters allow you to make sure that a global object is initialized before any of your code can try to use it. They also allow you to clean up the object after everything has finished using it. Follow the jump for more info about Nifty Counters and a full example.

That does sound nifty, but why would I want that?

MrTest is a simple unit testing library I created during the development of Number Duck. One of the goals of MrTest was to be able to create test cases easily as functions and have them automagically ran with no more work required.

// cool_test.cpp
#include 
TEST(cool_test)
{
	ASSERT_TRUE(DoThing());
}

Here is my simple test case defined like a function. With some Macro magic, this is what the above code expands into:

// cool_test.cpp
#include 
class cool_test
{
	cool_test()
	{
		MrTest::AddTest(&TestBody);
	}	
	void TestBody();
} cool_test_object;

void cool_test::TestBody()
{
	ASSERT_TRUE(DoThing());
}

From this we can see that the cool_test_object will be initialized during startup, adding our test case for MrTest to run later. So we've met the goal of being able to simply define and run our tests, but we have one problem. If cool_test is constructed before MrTest, then our code will crash, since we can't control the order of initialization. Or can we?

How can we control initialization order?

Very simply, we make another object that is created during startup, like cool_test, but we put it into the MrTest.h header file.

//MrTest.h
class MrTest;

class MrTest
{
	public:
		typedef void (TestFunction)();
		
		MrTest();
		~MrTest();

		static void AddTest(TestFunction* pTestFunction);
};

static class Initializer
{
	public:
		Initializer();
		~Initializer();
	
		static MrTest* m_pMrTest;
} initializer;

Since it's in the same header file as MrTest, the initializer object will be added to every cpp file *before* any code that tries to use MrTest. That's the Nifty part, the Counter part is that the Initializer class has reference counting, so it knows when to create and destroy MrTest.

// MrTest.cpp
MrTest* Initializer::m_pMrTest;
static int nifty_counter;
Initializer::Initializer()
{
	if (0 == nifty_counter++)
		m_pMrTest = new MrTest();
}

Initializer::~Initializer ()
{
	if (0 == --nifty_counter)
		delete m_pMrTest;
}

There's one more trick to this code. nifty_counter is a plain old static data type, and that means it is initialized to zero before any objects are created.

Are there any drawbacks to Nifty Counters?

One drawback of this method is that you ended up with initializer objects duplicated over your code, slowing down your starup and shutdown. Another drawback is that if you have the need for an object such as this, your design might be a bit too convoluted to begin with ;).

Previous: Charts!
Next: C++ std::string length