当类模板具有静态成员时,我们需要该成员的附加(模板化)定义。现在,该定义实际上并未立即实例化,而是需要实例化封闭模板,并且静态字段需要“使用”。到目前为止一切都很好。
但是,我在GCC / Linux上遇到了令人惊讶的行为。 (g ++ 4.7 和 7.2 )
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
class Factory
{
public:
T val;
Factory()
: val{}
{
cout << "Factory-ctor val="<<val<<endl;
}
};
template<typename T>
class Front
{
public:
static Factory<T> fac;
Front()
{
cout << "Front-ctor val="<<fac.val<<endl;
fac.val += 100;
}
T&
operate ()
{
cout << "Front-operate val="<<fac.val<<endl;
++ fac.val;
return fac.val;
}
};
template<typename T>
Factory<T> Front<T>::fac;
namespace {
Front<int> front;
int global_int = front.operate();
}
int
main (int, char**)
{
Front<int> fint;
int& i = fint.operate();
cout << "main: val="<<i<<endl;
cout << "global_int.......="<<global_int<<endl;
return 0;
}
在匿名命名空间中,我们首先创建Front的静态实例,然后在其上调用operate()
函数,使用静态工厂成员。输出和值都清楚地表明静态成员的ctor在使用后被调用。这种行为背后的原因是什么?这对我来说似乎有点直观:假设工厂管理一些资源,资源就会泄露。
~$ g++ --version
g++ (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0
~$ g++ --std=gnu++17 demo.cpp -o demo
~$ ./demo
Front-ctor val=0
Front-operate val=100
Factory-ctor val=0
Front-ctor val=0
Front-operate val=100
main: val=101
global_int.......=101
我也尝试过Clang(3.5),这只是段错误。
PS :明显的解决方法是将工厂变成Meyers Singleton。然而,我希望ctors和dtors系统在这种基本情况下是密不透风的(注意我们不是指其他翻译单位的任何静态)。因此,我主要对推理解释该观察感兴趣。
答案 0 :(得分:2)
这通常被称为静态初始化顺序fiasco。
基本上,我们有三个具有静态存储持续时间的对象:front
,global_int
和Front<int>::fac
。我们来自basic.start.dynamic:
如果变量是隐式或显式实例化的特化,则具有静态存储持续时间的非局部变量的动态初始化是无序的,如果变量是不是隐式或显式实例化的特化的内联变量,则是部分排序的,并且否则是订购。
所以Front<int>::fac
是无序的,其他两个是有序的。我们知道front
在global_int
之前已初始化,因为它们按定义顺序排序。但Front<int>::fac
与其他两个不确定地排序。
基本上发生的事情是静态初始化首先发生(零初始化),然后,稍后,您的Factory
构造函数实际运行 - 在您真正想要它之后的某个时间。
您只需标记构造函数Factory
即可。
或者,您可以根据Meyers单身人士将constexpr
包装到一个函数中。