Filip S.
Filip S.

Reputation: 1544

Initialization of static member with same type as class (static initialization order problem)

Say I have a class Super, in which I have a static member of type Super, which just defines a commonly used instance of Super

// super.hpp
class Super
{
public:
    Super(const double a):
        m_a(a)
    {}

    double a() const { return m_a; }

    static const Super thing; // use this a lot in code

private:
    double m_a;
};

with implementation

// super.cpp
#include "super.hpp"

const Super Super::thing = Super(1.0); // definition

At this point I think everything is okay, but please correct me if not.

Now I also have subclasses of Super, with similar static members

// sub.hpp
#include "super.hpp"

class Sub : public Super
{
public:
    Sub(const double a):
        Super(a)
    {}

    explicit Sub(const Super& obj):
        Super(obj)
    {}

    static const Sub thing;
};

with implementation

// sub.hpp
#include "sub.hpp"

const Sub Sub::thing = Sub(Super::thing); // WORKS IN MYSTERIOUS WAYS

And finally an example usage

// main.cpp
#include <iostream>
#include "sub.hpp"

int main()
{
    Super super = Super::thing;
    std::cout << super.a() << std::endl;

    Sub sub = Sub::thing;
    std::cout << sub.a() << std::endl;
}

When I compile this with the following setup

// CMakeLists.txt
project (hello)
add_executable(hello main.cpp super.cpp sup.cpp)

I get the expected output

$ ./hello 
1
1

but if I change the order of the cpp files in CMakeLists (sub.cpp before super.cpp)

// CMakeLists.txt
project (hello)
add_executable(hello main.cpp sup.cpp super.cpp)

I get

$ ./hello 
1
0

I think this is an example of static initialization order ‘fiasco’(?), and I have some understanding of why it happens.

So the question becomes: is there any way to get a warning about an un-initialized static, or any way to avoid the issue?

I've read How do I prevent the “static initialization order problem”?, but I was hoping to be able to keep the Sub::thing interface, and avoid replacing it with Sub::thing().

Upvotes: 1

Views: 83

Answers (2)

Jarod42
Jarod42

Reputation: 217275

is there any way to get a warning about an un-initialized static,

I don't know

or any way to avoid the issue?

  • You might avoid global member, and use lazy initialization wrapping the global in a function, so changing:

    // header
    class Super {
        static const Super thing;
        // ...
    };
    //  cpp file
    const Super Super::thing = Super(1.0); // definition
    

    to

    // header
    class Super {
        static const Super& thing();
        // ...
    };
    //  cpp file
    const Super& Super::thing() { static const Super instance{1.0}; return instance; }
    

    and similarly

    class Sub : public Super
    {
    public:
        // ...
        static const Sub thing;
    };
    
    // sub.cpp
    const Sub Sub::thing = Sub(Super::thing); 
    

    by

    class Sub : public Super
    {
    public:
        // ...
        static const Sub& thing();
    };
    
    // sub.cpp
    const Sub& Sub::thing() { static const Sub instance(Super::thing()); return instance; }
    
  • Or else place all global in a single translation unit, as order is guarantied.

    // global.cpp
    #include "super.hpp"
    #include "sub.hpp"
    
    const Super Super::thing = Super(1.0);    // Defined in order
    const Sub Sub::thing = Sub(Super::thing); // so you have control
    

Upvotes: 1

geza
geza

Reputation: 29952

Based on your comments, I did the following:

  • made constructors constexpr
  • made static members constexpr inline (you have to put them into the header file, not the .cpp), so C++17 is needed

Now, your static variables are constexpr, so they will be statically-initialized, so the static initialization order fiasco doesn't happen for them.

So, this solution works for me:

class Super
{
public:
    constexpr Super(const double a):
        m_a(a)
    {}

    double a() const { return m_a; }

    static const Super thing;

private:
    double m_a;
};

constexpr inline Super Super::thing = Super(1.0);

class Sub : public Super
{
public:
    constexpr Sub(const double a):
        Super(a)
    {}

    constexpr explicit Sub(const Super& obj):
        Super(obj)
    {}

    static const Sub thing;
};

constexpr inline Sub Sub::thing = Sub(Super::thing);

#include <iostream>

int main()
{
    Super super = Super::thing;
    std::cout << super.a() << std::endl;

    Sub sub = Sub::thing;
    std::cout << sub.a() << std::endl;
}

Upvotes: 1

Related Questions