sorry for the silence; grad school is intense.

in this post i explain how to create what i call an “static action registrar system” in C++ code. “registrar system” because it allows you to add things to a global registry. “action” because you register actions (functions). “static” because the technique is based on static objects being initialized before the program begins execution.

background

for awhile i was interested in Boost.Test library. specifically, it does this trick i couldn’t quite figure out at first. in a nutshell, the way the library works is that you include a couple headers, use special Boost.Test macros like BOOST_AUTO_TEST_CASE( my_test ) to make your suites and tests, and then compile and link it against the Boost.Test library. when you run your program, the test functions are magically run in order with some lovely safety nets to catch errors and early termination.

a typical use (excerpt) might look like:

#define BOOST_TEST_MODULE gordon
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE( suite1 );

BOOST_AUTO_TEST_CASE( test1 )
{
    BOOST_TEST_REQUIRE( 1 == 1 );
}

BOOST_AUTO_TEST_CASE( test2 )
{
    BOOST_TEST_WARN( 1 > 3 );
}

BOOST_AUTO_TEST_SUITE_END();

when run under --log_level=all, the output is:

Running 2 test cases...
Entering test module "gordon"
main.cpp:5: Entering test suite "suite1"
main.cpp:7: Entering test case "test1"
main.cpp:9: info: check 1 == 1 has passed
main.cpp:7: Leaving test case "test1"; testing time: 71us
main.cpp:12: Entering test case "test2"
main.cpp:14: warning: in "suite1/test2": condition 1 > 3 is not satisfied [1 <= 3]
Test case suite1/test2 did not check any assertions
main.cpp:12: Leaving test case "test2"; testing time: 46us
main.cpp:5: Leaving test suite "suite1"; testing time: 142us
Leaving test module "gordon"; testing time: 155us

*** No errors detected

as far as usability goes, there’s not much more i could want. however, the way Boost.Test actually manages to call these functions seems magical. i just declared them with a bit of extra macro sugar; how does the executable get the list and manage to invoke the functions just because i included the right header? i decided to poke around in the Boost.Test source until i understood what was going on. i’m not going to go in-depth on the details here, but rather demonstrate how you can do something similar in your own code.

a simple registrar

the goal of this section is to provide a header defining a unique macro semantically equivalent to BOOST_AUTO_TEST_CASE. in other words, we want to essentially use this macro like a function signature. we’ll be writing a code block after the macro invocation that’s tied to a namespace-unique identifier and gets registered somewhere for calling somewhere else in the program.

the key to this registrar trick is initialization, specifically the process referred to as “ordered dynamic initialization”:

Ordered dynamic initialization, which applies to all other non-local variables: within a single translation unit, initialization of these variables is always sequenced in exact order their definitions appear in the source code. Initialization of static variables in different translation units is indeterminately sequenced. Initialization of thread-local variables in different translation units is unsequenced.

here, “non-local variables” means any variable that isn’t a class template static data member.

dynamic initialization is guaranteed to happen either before main is executed, or before anything from the same translation unit is used in any way (technically, “odr-used”, which is a concept i don’t quite understand but take to be pretty much the same thing to anyone who isn’t a language lawyer).

the point is that we can leverage non-local variables in order to gather a list of functions we’d like to call at the start of a program. we do this by making use of the fact that object constructors are called during dynamic initialization.

in order to maintain a list of functions to run, we’re going to need some collection to hold function pointers. this implies that we’ll need some non-local object with a container and a “register” member function. let’s call the class registry and the registration function add_entry(). the container will be a std::vector, since we have no compelling reason to use anything else (yet).

we’ll also need to define the type of the function that we’re registering. for simplicity’s sake i’ve chosen void (*)().

i’ve also chosen to design this registry class as a singleton object; another option would be to have a plain global instance, which would allow you to have multiple registries for separate uses of this pattern.

so far, this is what our registry looks like:

// registry.hpp
#pragma once
#include <vector>

using reg_entry_t = void (*)();

class registry {
public:
    static registry& instance()
    {
        static registry inst{};
        return inst;
    }

    void add_entry( reg_entry_t const entry )
    {
        m_entries.push_back( entry );
    }

private:
    std::vector<reg_entry_t> m_entries;
};

we’re off to a good start, but we’re still missing a way to register these functions during dynamic initialization. in order to do that, we need something that can be constructed outside of the header, whose construction will involve calling add_entry. we can do this by creating a helper struct whose constructor takes a function to register and a reference to the registry:

struct registry_helper {
    registry_helper( reg_entry_t const func, registry& reg )
    {
        reg.add_entry( func );
    }
};

for each function we want to register, we can define a static, non-local instance of this struct. now we’re ready to write our registry macro!

#define REGISTER_ACTION( action_name )          \
    void action_name();                         \
    static registry_helper reg_ ## action_name{ \
        &action_name,                           \
        registry::instance()                    \
    };                                          \
    void action_name()

first, we declare the function to be registered, then our registry_helper instance, and finally provide a function definition stub for the block following the macro. we use preprocessor token pasting (##) to provide a reasonably unique name for the detail struct.

now we have a simple and idiomatic macro that clients can use to register their functions with the global registry. all that we have left to do is provide a way to invoke the contents of the registry. i’ve chosen to provide this via a run_all() method.

here’s the full header:

#pragma once

#include <vector>

using reg_entry_t = void (*)();

class registry {
public:
    static registry& instance()
    {
        static registry inst{};
        return inst;
    }

    void add_entry( reg_entry_t const entry )
    {
        entries.push_back( entry );
    }

    void run_all( void ) const
    {
        for ( auto const entry : entries )
            entry();
    }

private:
    std::vector<reg_entry_t> entries;
};

struct registry_helper {
    registry_helper( reg_entry_t const func, registry& reg )
    {
        reg.add_entry( func );
    }
};

#define REGISTERED_ACTION( action_name )       \
    void action_name();                        \
    static registry_helper reg ## action_name{ \
        &action_name,                          \
        registry::instance()                   \
    };                                         \
    void action_name()

altogether, it’s a little under 50 lines of code. here’s a short sample program to try it out:

#include "registry.hpp"
#include <iostream>

REGISTERED_ACTION( first_action )
{ std::cout << "first entry!" << std::endl; }

REGISTERED_ACTION( second_action )
{ std::cout << "second entry!" << std::endl; }

int main()
{
    registry::instance().run_all();
    return 0;
}

the output is exactly what you’d expect. note that in this case we’re including a main() in the same file as our registered actions. normally, you wouldn’t do that at all; the actions would be registered by other client code in totally separate TUs.

extensions

there are, of course, many ways to extend this design. we could:

  • have several registries and provide different macros for registering into each.
  • register the function name and other metadata (like __FILE__ and __LINE__) in addition to the function itself.
  • add actions to be performed before or after each function is called, like providing access to a mock object or cleaning up some global state.
  • change the function type so that registered actions can return status codes or other feedback.
  • provide macros for namespacing like Boost.Test does. i haven’t thought about how to do this yet, but it would be an interesting extension.
  • make registry a template class so that we can have multiple registries all taking separate functions types.

conclusion

hopefully this simplified version of Boost.Test’s test registration scheme is useful, or at least enlightening for you as it was for me. aside from finding it an interesting design solution, i’m looking into this approach as a possible way to simplify primitive definition and registration for the SuperCollider project. right now, language primitives have to be written in one part of the file, and made visible to the interpreter in a separate initFooPrimitives area of the file. this leads to a lot of redundancy and lack of consistency, and i think with a little tweaking this would be the perfect tool to clean that up.

my music recommendation for this post is Ramleh’s Hole in the Heart.