在模板中初始化静态成员
Initialization of a static member inside a template
这是一个最小的例子:
#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 Studio2015 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]:
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5)
before any other initialization takes place. A constant initializer for an object o is an expression that is a
constant expression, except that it may also invoke constexpr constructors for o and its subobjects even
if those objects are of non-literal class types. [ ... ]
Together, zero-initialization and constant initialization are called static initialization; all other initialization is
dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
在我们的例子中,b
是静态初始化的,而 b.x
是动态初始化的(构造函数不是 constexpr)。但我们还有:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage
duration is done before the first statement of main. If the initialization is deferred to some point in time
after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable
defined in the same translation unit as the variable to be initialized.
Odr 使用的方法,来自 [basic.def.odr]:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying
the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any nontrivial
functions and, if [ ... ]
但是计算 b.x
不会产生常量表达式,所以我们可以到此为止 - b.x
被 A<N>::foo()
odr-used ,这也是第一个odr-use。因此,虽然初始化不必发生在 main()
之前,但它必须发生在 foo()
之前。所以如果你得到 0,那就是编译器错误。
我倾向于这样写代码:
struct B {
B() {}
static int x;
};
int B::x = 42;
毕竟,static (x) 是在最后一行定义的(因此应该初始化)。将初始化放在 B 的构造函数中意味着每次构造 B 时都会重新初始化静态 x(只有其中一个!)。有一个静态,您应该只初始化一次。
这是一个最小的例子:
#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 Studio2015 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]:
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [ ... ]
Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
在我们的例子中,b
是静态初始化的,而 b.x
是动态初始化的(构造函数不是 constexpr)。但我们还有:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized.
Odr 使用的方法,来自 [basic.def.odr]:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any nontrivial functions and, if [ ... ]
但是计算 b.x
不会产生常量表达式,所以我们可以到此为止 - b.x
被 A<N>::foo()
odr-used ,这也是第一个odr-use。因此,虽然初始化不必发生在 main()
之前,但它必须发生在 foo()
之前。所以如果你得到 0,那就是编译器错误。
我倾向于这样写代码:
struct B {
B() {}
static int x;
};
int B::x = 42;
毕竟,static (x) 是在最后一行定义的(因此应该初始化)。将初始化放在 B 的构造函数中意味着每次构造 B 时都会重新初始化静态 x(只有其中一个!)。有一个静态,您应该只初始化一次。