在此示例中,当实例化类成员时,将初始化非模板类的内联静态成员变量c2
,而未对模板类的成员变量c1
进行初始化。有什么区别?为什么c1
不会被初始化,除非我通过获取其地址来强制它并且c2
被无条件地初始化?
struct C1 {
C1() { std::cerr << "C1()\n"; }
};
struct C2 {
C2() { std::cerr << "C2()\n"; }
};
template<typename T>
struct Template {
inline static C1 c1;
};
struct Nontemplate {
inline static C2 c2;
};
int main() {
Template<int> a;
Nontemplate b;
(void)a;
(void)b;
}
// Output:
C2()
以下是最小示例的上下文。我有从Nontemplate
继承的Template<something>
类,并且c2
的构造函数取决于c1
。我希望在c1
之前创建c2
;但是,事实并非如此。
template<typename T>
struct Template {
inline static C1 c1;
};
struct Nontemplate : public Template<int> {
struct C2 {
C2() {
std::cerr << "Do something with Nontemplate::C1\n";
std::cerr << "&Nontemplate::c1 = " << &Nontemplate::c1 << "\n";
}
};
inline static C2 c2;
};
int main() {
Nontemplate b;
(void)b;
}
// Output:
Do something with Nontemplate::C1
&Nontemplate::c1 = 0x600ea8
C1()
该代码是使用带有-std=c++17
标志的g ++ 7.2编译的。 -O0
和-O2
给出相同的结果。
类模板的隐式实例化仅导致其包含的声明的实例化。通常,仅在需要定义存在的上下文中使用定义时才实例化它们。
只要您不以需要其定义存在的方式(即通过使用odr使用它)来使用Template<int>::c1
,就根本不会对其进行定义。
使用odr使用变量的一种方法是获取其地址,如您所述。
即使您强制实例化变量,也无法保证何时将其完全初始化。
C1
的构造函数不是constexpr
,因此Nontemlate::c1
的初始化不能是常量表达式。这意味着您将获得Template<int>::c1
的动态初始化。属于模板专业化的全局静态变量的动态初始化为[[unordered,这意味着与全局静态变量的任何其他动态初始化相比,无法保证它们将以什么顺序发生。
Nontemlate::c2
没有通过常量表达式初始化,因此也被动态初始化。尽管Nontemlate::c2
具有部分排序的动态初始化
(属于inline
变量,而不是模板专业化的一部分),但相对于Template<int>::c1
,它仍然不确定地排序,如上所述。也不严格要求在输入Template<int>::c1
之前先初始化Nontemlate::c2
和main
。由实现方式定义是否将初始化推迟到以后,但在第一次使用相应变量之前。我认为此延迟主要用于运行时动态库加载。一种避免对全局静态存储持续时间变量进行排序的常见方法是使用返回对本地静态变量的引用的函数,即:
template<typename T>
struct Template {
static auto& c1() {
static C1 instance;
return instance;
}
};
尽管这在经常调用时可能会对性能产生影响,因为必须每次都检查局部静态变量的构造。,这意味着根本不会发生动态初始化,也不会出现排序问题。或者,如果可以使初始化程序成为一个常量表达式,则使变量
constexpr
应该保证常量初始化