【问题标题】:initialisation point of template static member模板静态成员的初始化点
【发布时间】:2018-05-01 19:03:41
【问题描述】:

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

但是,我对 GCC / Linux 的行为令人惊讶。 (g++ 4.77.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 系统在这种基本情况下是无懈可击的(请注意,我们不是指来自其他翻译单元的任何静态)。因此,我主要对解释该观察的推理感兴趣。

【问题讨论】:

    标签: c++ gcc c++14


    【解决方案1】:

    这通常被称为静态初始化顺序失败,相当恰当。

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

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

    所以Front&lt;int&gt;::fac 是无序的,另外两个是有序的。我们知道frontglobal_int 之前初始化,因为它们是按定义顺序排序的。但是Front&lt;int&gt;::fac 与其他两个的排序不确定。

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

    可以做的一件事是强制您的Factory 的初始化为常量初始化 - 这会将其移动到动态初始化之前。你可以通过标记你的构造函数constexpr来做到这一点。

    或者,您可以按照 Meyers 单例将 static 包装到一个函数中。

    【讨论】:

    • 感谢您的解释。我知道带有多个 *.cpp 翻译单元的“静态初始化命令失败”;这似乎是合乎逻辑的(但不幸的是)。但是这里我们有一个 single 翻译单元。什么实际问题阻止编译器在这里做正确的事情?使用模板化静态变量故意与普通静态变量不同,该语言有什么好处?
    • @Ichthyo 一个问题可能是:Front&lt;T&gt;::facFront&lt;U&gt;::fac 之间的顺序是什么?
    • 好点。但这并不排除在使用它们的同一个翻译单元中的任何表达式之前发出它们。
    • @Ichthyo 如果我要在一个 TU 中在 Front&lt;U&gt;::fac 之前实例化 Front&lt;T&gt;::fac,在另一个 TU 中在 Front&lt;T&gt;::fac 之前实例化 Front&lt;U&gt;::fac,那么最终的可执行文件应该遵循哪个顺序?
    • 你正在俯瞰房间里的大象:只要Front&lt;T&gt;::fac被使用之前被初始化 i> 在整个程序中是第一次,Front&lt;T&gt;::fac 也是如此。因为,如果 Factory 已经被使用过然后被初始化,它会泄露它所管理的资源。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 2012-02-02
    • 1970-01-01
    • 2011-03-14
    相关资源
    最近更新 更多