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.
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:
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
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 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:
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:
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!
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
##) 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
here’s the full header:
altogether, it’s a little under 50 lines of code. here’s a short sample program to try it out:
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.
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
__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.
registrya template class so that we can have multiple registries all taking separate functions types.
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.