静态内联成员初始化顺序
Static inline members initialization order
C++ 中一个众所周知的问题是 static initialization order fiasco。当使用 C++17 static inline members 时,它仍然被认为是一个问题吗?
这是一个静态内联成员在两个不同的翻译单元(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{};
};
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() { }
输出(使用 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
是 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,所以也可以。
C++ 中一个众所周知的问题是 static initialization order fiasco。当使用 C++17 static inline members 时,它仍然被认为是一个问题吗?
这是一个静态内联成员在两个不同的翻译单元(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{};
};
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() { }
输出(使用 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
是 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,所以也可以。