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
?
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.