初始化模板内的静态成员

Initialization of a static member inside a template

本文关键字:静态成员 初始化      更新时间:2023-10-16

这里有一个最小的例子:

#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++是正确的,因为在foo中有对b的引用,所以应该调用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。

从〔basic.start.init〕:

静态存储持续时间为(3.7.1(或线程存储持续时间(3.7.2(的变量应初始化为零(8.5(在进行任何其他初始化之前。对象o的常量初始值设定项是常量表达式,除了它还可以调用o及其子对象的constexpr构造函数之外如果这些对象是非文字类类型。[…]

零初始化和常量初始化统称为静态初始化;所有其他初始化动态初始化。应在进行任何动态初始化之前进行静态初始化。

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

实现定义了是否使用静态存储对非本地变量进行动态初始化持续时间在main的第一个语句之前完成。如果初始化推迟到某个时间点在main的第一个语句之后,它应该发生在任何函数或变量的第一次odr使用(3.2(之前在与要初始化的变量相同的转换单元中定义。

Odr使用的手段,来自〔basic.def.Odr〕:

变量x的名称显示为可能求值的表达式ex,除非应用从左值到右值的转换(4.1(到x产生了一个常数表达式(5.20(,它不会调用任何非平凡的功能,如果[…]

但是计算b.x并不能得到一个常数表达式,所以我们可以到此为止——b.xA<N>::foo()使用的odr,这也是第一个odur使用。因此,虽然初始化不必在main()之前发生,但它必须在foo()之前发生。所以如果你得到0,那就是编译器错误。

我倾向于这样写代码:

struct B {
   B() {} 
   static int x;
};
int B::x = 42;

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