c++static-constructorstatic-initialization

Imitate a static constructor in C++


This a question related to the initialization of objects in C++.

I have a group of classes (not instances), inheriting from a common base class, and I need them to register info about themselves in a container (specifically a map) when the program starts.

The problem is that I need it to be dynamic. The container is defined in an independent project, different from the classes. I would prefer to avoid making multiple hard-coded versions of the library, one for each set of classes in each program that uses it.

I thought about having a static instance of a special class in each of these subclasses, that would make the registration in its constructor. However, I have found no way to guarantee that the container will be constructed before the construction of these objects.

I should also note that the information in the container about the subclasses should be available before any instance of these subclasses is created.

Is there a way to do this, or imitate a static constructor in C++ in general?


Solution

  • You are describing different problems all at once. On the particular issue of having some sort of static initialization, a simple approach is creating a fake class that will perform the registration. Then each one of the different classes could have a static const X member, the member will have to be defined in a translation unit, and the definition will trigger the instantiation of the instance and the registration of the class.

    This does not tackle the hard problem, which is the initailization order fiasco. The language does not provide any guarantee on the order of initialization of objects in different translation units. That is, if you compile three translation units with such classes, there is no guarantee on the relative order of execution of the fake member. That is also applied to the library: there is no guarantee that the container in which you want to register your classes has been initialized, if such container is a global/static member attribute.

    If you have access to the code, you can modify the container code to use static local variables, and that will be a step forward as to ensure the order of initialization. As a sketch of a possible solution:

    // registry lib
    class registry { // basically a singleton
    public:
       static registry& instance() { // ensures initialization in the first call
          static registry inst;
           return inst;
       }
    // rest of the code
    private:
       registry(); // disable other code from constructing elements of this type
    };
    // register.h
    struct register {
       template <typename T>
       register( std::string name ) {
           registry::instance().register( name, T (*factory)() ); // or whatever you need to register
       }
    };
    // a.h
    class a {
    public:
       static a* factory();
    private:
       static const register r;
    };
    // a.cpp
    const register a::r( "class a", a::factory );
    // b.h/b.cpp similar to a.h/a.cpp
    

    Now in this case there is no definite order among the registration of the a and b classes, but that might not be an issue. On the other hand, by using a local static variable in the registry::instance function the initialization of the singleton is guaranteed to be performed before any call to the registry::register methods (as part of the first call to the instance method).

    If you cannot make that change you are basically out of luck and you cannot guarantee that the registry will be instantiated before the other static member attributes (or globals) in other translation units. If that is the case, then you will have to postpone the registration of the class to the first instantiation, and add code to the constructor of each class to be registered that ensures that the class is registered before actual construction of the object.

    This might or not be a solution, depending on whether other code creates objects of the type or not. In the particular case of factory functions (first one that came to mind), if nothing else is allowed to create objects of types a or b... then piggy backing registration on constructor calls will not be a solution either.