6

这是一个最小的例子:

#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++ 是正确的,因为有对binside的引用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。

4

2 回答 2

6

来自 [basic.start.init]:

具有静态存储持续时间 (3.7.1) 或线程存储持续时间 (3.7.2) 的变量应在任何其他初始化发生之前进行零初始化 (8.5)。对象 o 的常量初始化器是一个常量表达式,除了它还可以为 o 及其子对象调用 constexpr 构造函数,即使这些对象是非文字类类型。[ ... ]

零初始化和常量初始化合称为静态初始化;所有其他初始化都是动态初始化。静态初始化应在任何动态初始化发生之前执行。

在我们的例子中,b是静态初始化的,但是b.x是动态初始化的(构造函数不是 constexpr)。但我们也有:

具有静态存储持续时间的非局部变量的动态初始化是否在 main 的第一条语句之前完成是实现定义的。如果初始化延迟到 main 的第一个语句之后的某个时间点,它应该发生在与要初始化的变量在同一翻译单元中定义的任何函数或变量的第一次 odr-use (3.2) 之前。

Odr 使用的意思是,来自 [basic.def.odr]:

变量 x 其名称显示为潜在求值表达式 ex 被 ex odr 使用,除非将左值到右值转换 (4.1) 应用于 x 产生不调用任何非平凡函数的常量表达式 (5.20),并且,如果[ ... ]

但是评估b.x不会产生一个常量表达式,所以我们可以停在那里 - b.xis odr-used by A<N>::foo(),这也是第一个odr-use。因此,虽然初始化不必发生在之前main(),但它确实必须发生在之前foo()。所以如果你得到 0,那就是编译器错误。

于 2015-07-13T13:05:18.333 回答
-2

我倾向于这样编写代码:

struct B {
   B() {} 
   static int x;
};

int B::x = 42;

毕竟,静态 (x) 是在最后一行定义的(因此应该被初始化)。将初始化放在 B 的构造函数中意味着静态 x(只有其中一个!)将在每次构造 B 时重新初始化。有一个静态,您应该只初始化一次。

于 2015-07-13T13:06:12.337 回答