【发布时间】:2019-01-01 23:52:19
【问题描述】:
我试图了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁的排序规则。考虑这两个类:
struct Foo {
Foo() { std::cout << "Foo\n"; }
~Foo() { std::cout << "~Foo\n"; }
static Foo &instance();
};
struct Bar {
Bar() { std::cout << "Bar\n"; }
~Bar() { std::cout << "~Bar\n"; }
static Bar &instance();
};
除了静态instance成员函数的实现之外,它们是相同的:
thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }
Bar &Bar::instance() { static Bar s_bar; return s_bar; }
Bar 是一个 Meyers 单例,一个具有静态存储持续时间的块范围对象。
Foo 的实例是具有线程本地存储持续时间的命名空间范围对象。
现在是main 函数:
int main() {
Bar::instance();
Foo::instance();
}
这是 GCC 8.1.0 和 Clang 5.0.0 的输出:
Bar
Foo
~Foo
~Bar
现场试用:https://coliru.stacked-crooked.com/a/f83a9ec588aed921
我曾期望首先构造Foo,因为它在命名空间范围内。我想允许实现将初始化推迟到对象的第一次 odr 使用。我不知道它可以推迟到块范围静态初始化之后,但我可以接受。
现在我颠倒main中函数调用的顺序:
int main() {
Foo::instance();
Bar::instance();
}
这是输出:
Foo
Bar
~Foo
~Bar
现在我已经把Foo实例的第一个odr-use移到第一次调用Bar::instance之前,初始化的顺序和我预期的一样。
但我认为对象应该按照初始化的相反顺序销毁,这似乎没有发生。我错过了什么?
关于静态和线程本地存储持续时间的对象的初始化和销毁,cppreference 和标准说诸如“程序启动时”、“线程启动时”、“程序结束时”和“线程何时结束”,但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,是第一个线程和最后一个线程?
在我的“真正”问题中,Foo(线程本地)被记录器使用,而Bar 的基类的析构函数使用记录器,所以这是一个静态销毁命令惨败。产生了其他线程,但Bar(Meyers 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不必只是随机尝试。
【问题讨论】:
-
这里有几个无端的意见:(1)你要理解语言的棘手语义会遇到很多麻烦。我会花时间尝试编写 Joe Average 开发人员无需咨询语言规范即可阅读、理解和安全修改的代码。 (2) 理解
static变量会很麻烦。我会花时间尝试从我的设计中消除static变量。 -
@SolomonSlow 我同意这两点,这是我的长期目标。但是今天我只想修复一个错误而不重构整个系统。
标签: c++ multithreading static c++14 static-order-fiasco