Search code examples
c++moduleextern

Queuing a call in headers


The goal is to allow header files to "register" an initializer function so that main can just iterate over those functions and call them. I've stumbled upon a solution which uses __attribute__, but it seems to be GCC-only (https://stackoverflow.com/a/37082249/7867841).

// header1.h
void myInitializer(){}
REGISTER_THIS(&myInitializer);

// header2.h
void myInitializer2(){}
REGISTER_THIS(&myInitializer2);

// main.cpp
...
for_each_registered_ptr(){ call_ptr(); } // calls myInitializer and myInitializer2
...

Is there a universal solution to this? Functions can be switched with classes or types if that's easier to implement.


Solution

  • You can abuse static function locals to do this, avoiding the static initialization order fiasco.

    In init.h, we have this:

    #ifndef INIT_H
    #define INIT_H
    
    #include <vector>
    
    // Can be changed to std::function<...> or whatever you need.
    typedef void (*init_fn)();
    
    // Returns int so it can easily be used in a variable initializer.
    int register_initializer(init_fn fn);
    std::vector<init_fn> & get_initializers();
    
    #endif
    

    Then, in init.cpp:

    #include "init.h"
    
    int register_initializer(init_fn fn)
    {
        get_initializers().push_back(fn);
    
        return 0;
    }
    
    std::vector<init_fn> & get_initializers()
    {
        static std::vector<init_fn> ip;
    
        return ip;
    }
    

    A few notes, before we move on to the rest:

    • The static local is only initialized once, the first time the function is called.
    • The "global" vector is kind-of-leaked. It's unlikely this will be a problem unless you are adding tens of thousands of entries to this vector. You can always get_initializers().clear() to empty it out after using it.

    We'd use it like so, in a.cpp:

    #include <iostream>
    
    #include "init.h"
    
    static void a_init() { std::cout << "a_init()\n"; }
    
    static auto dummy = register_initializer(a_init);
    

    And, finally, we have our (rather simple) main.cpp:

    #include "init.h"
    
    int main() {
        for (auto fn : get_initializers()) {
            fn();
        }
    
        return 0;
    }