C++ 模板静态成员的初始化点

C++ 模板静态成员的初始化点,c++,gcc,c++14,C++,Gcc,C++14,当类模板具有静态成员时,我们需要该成员的附加(模板化)定义。现在,该定义实际上没有立即实例化,而是需要实例化封闭模板,静态字段需要是“odr used”。到目前为止还不错 然而,我在GCC/Linux中得到了令人惊讶的行为。(g++4.7和7.2) 我还尝试了叮当声(3.5),这只是一个错误 PS:显而易见的解决办法是将工厂变成梅耶斯单身汉。然而,在这样一种基本情况下,我希望CTOR和DTOR系统是密封的(注意,我们不是指来自其他翻译单元的任何静态数据)。因此,我主要感兴趣的是解释这一观察结果

当类模板具有静态成员时,我们需要该成员的附加(模板化)定义。现在,该定义实际上没有立即实例化,而是需要实例化封闭模板,静态字段需要是“odr used”。到目前为止还不错

然而,我在GCC/Linux中得到了令人惊讶的行为。(g++4.7和7.2)

我还尝试了叮当声(3.5),这只是一个错误



PS:显而易见的解决办法是将工厂变成梅耶斯单身汉。然而,在这样一种基本情况下,我希望CTOR和DTOR系统是密封的(注意,我们不是指来自其他翻译单元的任何静态数据)。因此,我主要感兴趣的是解释这一观察结果的推理。

这通常被称为静态初始化顺序的失败

基本上,我们有三个具有静态存储持续时间的对象:
front
global\u int
,和
front::fac
。我们从:

如果变量是隐式或显式实例化的专用化,则具有静态存储持续时间的非局部变量的动态初始化是无序的;如果变量是不是隐式或显式实例化的专用化的内联变量,则动态初始化是部分有序的,否则是有序的

因此,
Front::fac
是无序的,其他两个是有序的。我们知道
front
是在
global\u int
之前初始化的,因为它们是按定义顺序排序的。但是
Front::fac
与其他两个序列不确定

基本上,发生的是静态初始化首先发生(零初始化),然后,稍后,您的
工厂
构造函数实际运行—在您实际希望它运行之后的某个时间

您可以做的一件事是强制您的
工厂
的初始化为常量初始化-这会使它先于动态初始化。只需标记构造函数
constexpr
,就可以实现这一点


或者,您可以按照Meyers单例将
静态
包装成一个函数

谢谢你的解释。我知道几个*.cpp翻译单元的“静态初始化顺序失败”;这似乎是合乎逻辑的(但很不幸)。但这里我们只有一个翻译单元。什么实际问题阻止编译器在这里做正确的事情?有一个模板化的静态变量故意表现出与普通静态变量不同的行为,这种语言有什么好处?@Ichthyo一个问题可能是:在
Front::fac
Front::fac
之间的顺序是什么?这一点很好。但这并不排除在使用它们的同一翻译单元中的任何表达式之前发出它们。@Ichthyo如果我在一个TU中实例化
Front::fac
之前的
Front::fac
,在另一个TU中实例化
Front::fac
之前的
,最终的可执行文件应该遵循哪种顺序?你忽略了房间里的大象:只要在整个程序中第一次使用之前对
Front::fac
进行了初始化,
Front::fac
,那么T和U的相对顺序就无关紧要了。因为,如果工厂已经被使用,然后被初始化,它将泄漏它管理的资源。
#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;
  }
~$ 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