这是一个最小的例子:
#include <iostream>
struct B {
B() { x = 42; }
static int x;
};
int B::x;
template <int N>
struct A {
int foo() { return b.x; }
static B b;
};
template<int N>
B A<N>::b;
//template struct A<2>; // explicit instantiation with N = 2 (!)
int main(int argc, char **argv) {
std::cout << A<1>().foo() << std::endl;
return 0;
}
该程序使用g ++ 4.9.2写入42,但使用Visual Studio 2015 RC写入0。此外,如果我取消注释显式实例化,VS2015RC也会给出42,这非常有趣,因为此处的模板参数与main
函数中使用的模板参数不同。
这是一个错误吗?我假设g ++是正确的,因为b
中有foo
的引用,因此应该调用B
的构造函数。
编辑:有一个简单的解决方法 - 如果B
中有一个非静态变量,在A
中引用,VS2015RC将正确编译:
// ...
struct B {
B() { x = 42; }
static int x;
int y; // <- non-static variable
};
// ...
template <int N>
struct A {
int foo() { b.y; return b.x; } // <- reference to b.y
static B b;
};
这似乎有效,即使b.y
作为陈述,显然是NOP。
答案 0 :(得分:6)
来自[basic.start.init]:
具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量应为零初始化(8.5) 在进行任何其他初始化之前。对象o的常量初始值设定项是a的表达式 常量表达式,除了它甚至可以为o及其子对象调用constexpr构造函数 如果这些对象是非文字类类型。 [...]
一起,零初始化和常量初始化称为静态初始化;所有其他初始化是 动态初始化。在进行任何动态初始化之前,应执行静态初始化。
在我们的例子中,b
是静态初始化的,但b.x
是动态初始化的(构造函数不是constexpr)。但我们也有:
实现定义是否使用静态存储动态初始化非局部变量 持续时间在主要的第一个陈述之前完成。如果初始化延迟到某个时间点 在第一个main语句之后,它应该在任何函数或变量的第一次使用(3.2)之前发生 在与要初始化的变量相同的翻译单元中定义。
Odr-used表示来自[basic.def.odr]:
变量x的名称显示为潜在评估的表达式ex,除非应用,否则由ex使用 左值到右值的转换(4.1)到x产生一个不调用任何非平凡的常量表达式(5.20) 功能,如果[...]
但评估b.x
不会产生常量表达式,因此我们可以停在那里 - b.x
使用 A<N>::foo()
,这也是第一个 odr-use 。因此,虽然初始化不必在main()
之前发生,但它必须在foo()
之前发生。所以如果你得到0,那就是编译器错误。
答案 1 :(得分:-2)
我倾向于写这样的代码:
struct B {
B() {}
static int x;
};
int B::x = 42;
毕竟,静态(x)是在最后一行定义的(因此应该初始化)。将初始化放在B的构造函数中意味着静态x(只有其中一个!)将在每次构造一个B时重新初始化。有一个静态,你应该只初始化一次。