下面的代码重现了我遇到的问题,MSVC 2022:
#include <iostream>
struct A {
static void message()
{
std::cout << "A::message()\n";
}
struct B {
B() {}
~B()
{
A::message();
}
};
static inline B b;
};
int main()
{
}
通过上面的代码,我看到如预期的那样出现了消息。然而:
B()
,那么析构函数~B()
似乎
由于消息消失而被删除。int dummy;
到结构 B
,则该消息会再次出现。我实在找不到答案的问题:
B()
并且 B
中没有属性,编译器只是将类 B
标记为空,然后完全跳过它。这是正确的吗?B()
是否足够,或者我还应该使用虚拟属性 B
使 int dummy;
非空吗?区别在于
b
是否有静态初始化。
如果静态存储持续时间变量具有静态初始化,则它会在任何其他没有静态初始化的静态存储持续时间变量之前初始化(即动态初始化)。
特别是,
<iostream>
中定义了另一个重要的静态存储持续时间对象,即std::ios_base::Init
的实例。该类型对象的初始化会导致标准 IO 流的初始化(例如 std::cout
)。当它的最后一个实例被销毁时,它还负责刷新所有这些流。
因此,为了查看您的输出,您必须确保
std::ios_base::Init
对象在您的b
之后被摧毁。然而,销毁的顺序与初始化的顺序相反。因此,如果
b
具有静态初始化,但
std::ios_base::Init
对象具有动态初始化,那么这是错误的方法。一般来说,如果静态存储持续时间变量的初始化是常量表达式,则该变量具有静态初始化。如果您不声明任何构造函数,就是这种情况。
如果初始化不是常量表达式,那么它通常具有动态初始化,只不过在许多情况下实现可以自由选择静态初始化。如果您使用非
constexpr
构造函数或未初始化成员,则会遇到这种情况。因此,为了可靠地获得正确的顺序,您需要确保
b
没有静态初始化。由于实施上有余地,这有点棘手。做一些显然只能在运行时发生的事情应该足够了(例如调用一些 IO),但标准当前指定的方式,很难完全确定。 (请参阅CWG 第 1294 期。) 更好的选择可能是添加一个
std::ios_base::Init
对象作为
B
的成员,这样当
b
的析构函数运行时,这样一个对象肯定会保持活动状态。