模板静态成员的初始化点

时间:2018-05-01 19:03:42

标签: c++ gcc c++14

当类模板具有静态成员时,我们需要该成员的附加(模板化)定义。现在,该定义实际上并未立即实例化,而是需要实例化封闭模板,并且静态字段需要“使用”。到目前为止一切都很好。

但是,我在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系统在这种基本情况下是密不透风的(注意我们不是指其他翻译单位的任何静态)。因此,我主要对推理解释该观察感兴趣。

1 个答案:

答案 0 :(得分:2)

这通常被称为静态初始化顺序fiasco。

基本上,我们有三个具有静态存储持续时间的对象:frontglobal_intFront<int>::fac。我们来自basic.start.dynamic

  

如果变量是隐式或显式实例化的特化,则具有静态存储持续时间的非局部变量的动态初始化是无序的,如果变量是不是隐式或显式实例化的特化的内联变量,则是部分排序的,并且否则是订购。

所以Front<int>::fac是无序的,其他两个是有序的。我们知道frontglobal_int之前已初始化,因为它们按定义顺序排序。但Front<int>::fac与其他两个不确定地排序。

基本上发生的事情是静态初始化首先发生(零初始化),然后,稍后,您的Factory构造函数实际运行 - 在您真正想要它之后的某个时间。

您只需标记构造函数Factory即可。

或者,您可以根据Meyers单身人士将constexpr包装到一个函数中。