【发布时间】:2016-10-12 23:24:14
【问题描述】:
考虑以下情况,我们在不同的翻译单元中有两个文件范围的对象,这是通过初始化顺序失败的方式为未定义行为的通常设置:
a.hpp:
struct thing {
public:
thing(int value);
~thing();
int value() const;
static int count();
private:
int _value;
};
a.cpp:
#include "a.hpp"
#include <atomic>
namespace {
std::atomic<int> things;
}
thing::thing(int value) : _value(value) {
++things;
}
thing::~thing() {
--things;
}
int thing::value() const {
return _value;
}
int thing::count() {
return things.load();
}
b.cpp:
#include <iostream>
#include "a.hpp"
namespace {
thing static_thing(42);
}
void foo() {
std::cout << static_thing.value() << ' ' << thing::count() << '\n';
}
此代码是否受制于 a.cpp 中范围为原子的文件 things 和 b.cpp 中范围为 static_thing 的文件之间的初始化顺序失败?如果不是,为什么不呢?特别是,std::atomic 有什么特别之处,它消除了原本清晰的 init 顺序惨败?是否有可以命名的特定概念以使用静态断言来强制执行此操作?比如:
static_assert(std::is_trivial<decltype(things)>::value, "file static counter is not trivial");
如果不是std::is_trivial,是否有另一个概念和相关的类型特征可以更好地模拟这个?
相反,是否存在反初始化惨败?如果是这样,为什么,或者为什么不这样,同样的问题。
【问题讨论】:
-
我对此事的简要研究表明,鉴于
std::atomic<int>必须是专门化的,并且专门化的原子具有某些属性,std::atomic<int>就像一个 POD,用于初始化顺序,例如没有惨败。但是,这种情况也可以像大多数其他受 init 失败的情况一样解决:但是在函数静态范围内声明静态对象,并利用函数静态范围对象必须在第一次进入之前初始化的事实函数。 -
静态对象在不同翻译中的构造顺序仍然未指定。使用
std::atomic不会改变这一点。你为什么会期望它?特定的编译器可能会首先实例化原子,但这不是必需的。 -
@Peter 因为 std::atomic
有一个 constexpr 构造函数,并且正在用一个常量初始化,并且有一个普通的析构函数。这表明该对象实际上是不朽的。由于静态初始化发生在任何动态初始化之前,并且由于没有什么可以破坏它,所以它基本上是不朽的。我看不出怎么会有初始化订单惨败w.r.t。任何具有动态初始化的东西。 -
在创建对象时发生惨败 - 随后永远存在(时间结束是程序终止)是无关紧要的。根据 C++14 3.6.2/2,常量初始化(包括
constexpr)在动态初始化之前并在零初始化之后,但问题是依赖于任何两个静态对象的未指定初始化顺序。当有两个带有constexpr构造函数的静态对象接受一个指针时,必须首先初始化两个指针,这需要构造指针。如果依赖于他们的构建顺序.....
标签: c++ c++11 static initialization undefined-behavior