Search code examples
c++initializationstatic-initializationinitialization-order

Static initialization order in class heirarchy


I've recently become painfully aware of the Static Initialization Order Fiasco. I am wondering though if the rule that "initialization order is undefined across translation units" still holds for static members in a parent class which are needed by static members in a child class.

For example, say we have (excluding, for brevity, all the # guards and includes)

// a.h
class A {
    static int count;
    static int register_subclass();
};

// a.cpp
int A::count = 0;
int A::register_subclass() {
    return count ++;
}

And then a sub-classes of A,

// b.h
class B : public A {
    static int id;
};

// b.cpp
int B::id = A::register_subclass();

There are two translation units here with static objects in one depending on static objects in the other on initialization... it seems like it may be an instance of the static initialization order fiasco.

My question is: is it actually safe?

That is, am I guaranteed that there is no chance that B::id will contain junk copied from A::count before the latter is initialized? From my own tests, A always seems to get initialized first, but I'm not sure how to introduce noise in the initialization order to increase the probability of failing if the behavior is undefined.


Solution

  • Generally, it is not safe to rely on the static initialization order of a base class and derived class. There is no guarantee that the static initialization of A will happen before B. That is the definition of the static initialization order fiasco.

    You could use the construct on first use idiom:

    // a.h
    class A {
    private:
        static int& count();
    protected:
        static int register_subclass();
    };
    
    // a.cpp
    int& A::count() {
        static int count = 0;
        return count;
    }
    
    int A::register_subclass() {
        return count()++;
    }
    
    // b.h
    class B : public A {
    public:
        static int id;
    };
    
    // b.cpp
    int B::id = A::register_subclass();
    

    Live demo.

    Update: However, saying that, bogdan pointed out in comments

    according to [3.6.2] in the Standard, the order of initialization in this specific example is guaranteed. It has nothing to do with inheritance, but with the fact that the initialization of A::count is constant initialization, which is guaranteed to be done before dynamic initialization, which is what B::id uses.

    But unless you have a complete grasp of such intracaccies I recommend you use the construct on first use idiom.

    And it is ok in this case but be careful of functions like A::register_subclass in a multi-threaded context. If multiple threads call it simultaneously anything could happen.