1

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().

Filip S.
  • 1,514
  • 3
  • 19
  • 40
  • My constructors are pretty simple (their bodies are empty, I just assign all values in the initialization list), so I probably can do that, but I would need some more guidance in how to do it. I'm not very familiar with `constexpr`. – Filip S. Oct 02 '19 at 07:52
  • I tried making `thing` an `inline static` instead of `static`, but I get an error because `Super` has incomplete type. – Filip S. Oct 02 '19 at 07:54
  • Yeah, I think the problem is that at point, the compiler doesn't see the complete definition of the class. Which is weird, because it doesn't need it at that point. It should work, if `thing` wasn't a member, just a global, outside of the class. I removed my suggestion. I'll try to do this properly later. – geza Oct 02 '19 at 07:57
  • I've found an answer to a similar question that might be useful: https://stackoverflow.com/a/32134757/1850917 – Filip S. Oct 02 '19 at 07:58
  • Btw., `constexpr` won't work, if you have a destructor, because your type won't be a literal type anymore. Just `inline` for the `thing` variable won't work I'm afraid, because it cannot be used in a `constexpr` context. So maybe, `constexpr` constructor is not the way to do then. – geza Oct 02 '19 at 08:00
  • I don't need a (non-default) destructor. – Filip S. Oct 02 '19 at 08:06
  • Can't you just overload the constructors to create those "commonly used instance"s? Or do you *really* need this: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter? – Bob__ Oct 02 '19 at 08:10
  • @Bob__ I can overload the constructor, but think I would need some more info on what exactly you mean. Meanwhile I'll have a look at Nifty Counter. – Filip S. Oct 02 '19 at 08:18
  • I mean that in the example code those static objects are only used to create new instances via copy construction. If this is the only use case, you could just add a default constructor to `Super` and `Sub`. Otherwise, you may add a more complex example to your question. – Bob__ Oct 02 '19 at 08:24
  • Yes, creating new instances via copy is the main use case. But I'm not sure how to get the wanted functionality (`Sub obj = Sub::thing`) via your method. But I'm probably missing something. – Filip S. Oct 02 '19 at 08:28
  • 1
    Well, I mean [this](https://wandbox.org/permlink/jm0GMNmKLeVsoxdZ), but again, it depends on your use case. – Bob__ Oct 02 '19 at 08:54

2 Answers2

1

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;
}
geza
  • 28,403
  • 6
  • 61
  • 135
  • This was the exact solution I was working on myself, and it seems to work well. I don't fully understand how I can have the definitions in the headers and not get multiple definition errors, but as long as it works! – Filip S. Oct 02 '19 at 08:58
  • 1
    @FilipS.: that's why `inline` needed. If you remove it, you can get linker error. – geza Oct 02 '19 at 09:01
1

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
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • That works, but my goal was to keep the `Super::thing` interface, which the currently accepted answer does. Thanks for the suggestion though! – Filip S. Oct 02 '19 at 13:30
  • There is still possibility to place the 2 global in same file, as initialization order is guarantied inside a TU. – Jarod42 Oct 02 '19 at 14:15