A well known problem in C++ is the static initialization order fiasco. Is it still considered a problem when one use C++17 static inline members?
Here an example where a static inline member is used in two different translation units (a.cpp and b.cpp) as an initializer for two non-inline static members:
counter.hh
#pragma once
#include <vector>
#include <fstream>
class Counter
{
public:
Counter() { std::ofstream os("o.txt", std::ofstream::app); os << "Counter created" << std::endl; }
~Counter() { std::ofstream os("o.txt", std::ofstream::app); os << "Counter destroyed" << std::endl; }
void add_instance()
{
++m_instances;
std::ofstream os("o.txt", std::ofstream::app); os << "Counter increased: " << m_instances << std::endl;
}
void remove_instance()
{
--m_instances;
std::ofstream os("o.txt", std::ofstream::app); os << "Counter decreased: " << m_instances << std::endl;
}
private:
int m_instances = 0;
};
class Object
{
public:
Object(Counter & counter) : m_counter(counter)
{
m_counter.add_instance();
std::ofstream os("o.txt", std::ofstream::app); os << "Object created" << std::endl;
}
~Object()
{
m_counter.remove_instance();
std::ofstream os("o.txt", std::ofstream::app); os << "Object destroyed" << std::endl;
}
private:
Counter & m_counter;
};
struct C
{
static inline Counter static_counter{};
};
a.hh
#pragma once
#include "counter.hh"
struct A
{
static Object static_a; //not inline
};
a.cpp
#include "a.hh"
Object A::static_a{C::static_counter};
b.hh
#pragma once
#include "counter.hh"
struct B
{
static Object static_b; //not inline
};
b.cpp
#include "b.hh"
Object B::static_b{C::static_counter};
main.cpp
#include "a.hh"
#include "b.hh"
int main() { }
output (with MSVC 16.1.2)
Counter created
Counter increased: 1
Object created
Counter increased: 2
Object created
Counter decreased: 1
Object destroyed
Counter decreased: 0
Object destroyed
Counter destroyed
I think that, with regard to the initialization, this practice is safe because the C++17 standard ensures that static inline members are: (1) always initialized before any use and (2) initialized only once across multiple translation units.
But I'd like to know if there are any hidden downsides in this pattern, for example related to the order of destruction of each variable across different TUs. Is it well-defined that both static_a
and static_b
are always destroyed before static_counter
?
C++ static member variables and their initialization. Static C++ member variables are defined using the static keyword. The static member variables in a class are shared by all the class objects as there is only one copy of them in the memory, regardless of the number of objects of the class. The static class member variables are initialized ...
The static initialization order fiasco refers to the ambiguity in the order that objects with static storage duration in different translation units are initialized in.
The static member variables in a class are shared by all the class objects as there is only one copy of them in the memory, regardless of the number of objects of the class.
If an object in one translation unit relies on an object in another translation unit already being initialized, a crash can occur if the compiler decides to initialize them in the wrong order. For example, the order in which .cpp files are specified on the command line may alter this order.
Yes, this is fine, since in every translation unit static_counter
is defined before static_a
/static_b
. Destruction order is not guaranteed to be the reverse (given threads, this is meaningless anyway), but the reverse of each guarantee holds, so that works too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With