Хорошо известная проблема в C++ — это фиаско статического порядка инициализации. По-прежнему считается ли проблемой использование статических встроенных членов C++17?
Вот пример, где статический встроенный член используется в двух разных единицах трансляции (a.cpp и b.cpp) в качестве инициализатора для двух невстроенных статических членов:
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{};
};
а.хх
#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};
б.чч
#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() { }
вывод (с 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
Я думаю, что в отношении инициализации эта практика безопасна, поскольку стандарт C++17 гарантирует, что статические встроенные члены: (1) всегда инициализируются перед любым использованием и (2) инициализируются только один раз для нескольких единиц трансляции.
Но я хотел бы знать, есть ли какие-либо скрытые недостатки в этом шаблоне, например, связанные с порядком уничтожения каждой переменной в разных TU. Правильно ли определено, что и static_a
, и static_b
всегда уничтожаются до static_counter
?
static_counter
будет инициализирован доstatic_a
/static_b
и гарантированно ли он будет уничтожен послеstatic_a
/static_b
. Я знаю, чтоstatic_a
иstatic_b
были бы опасны, если бы использовались для инициализации других статических членов, но меня интересует только связь междуstatic_counter
и другими глобальными переменными, которые зависят от них при построении. Может быть, я должен найти более ясный пример - person Tarquiscani   schedule 09.06.2019