Reputation: 1544
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
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
Reputation: 29952
Based on your comments, I did the following:
constexpr
static
members constexpr inline
(you have to put them into the header file, not the .cpp
), so C++17 is neededNow, 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