C++模板:未初始化全局对象中的静态成员

Wei*_*ong 12 c++ templates constructor initialization static-members

我有一段简单的C++代码,其中我通过专门化模板来定义模板和全局对象.对象构造函数访问专用模板中的静态成员.但事实证明静态成员在那时没有被初始化.但对于本地对象(在函数体中定义),它可以工作.我糊涂了...

我的c ++编译器是:g ++(Ubuntu 5.4.0-6ubuntu1~16.04.4)5.4.0 20160609

/////////////////////////
template<typename T>
class TB{
public:
  const char *_name;
  TB(const char * str):_name(str){
    cout << "constructor is called:" << _name << endl;
  };

  virtual ~TB(){
    cout << "destructor is called:" << _name << endl;
  };
};

template<typename T>
class TA{
public:
  const char *_name;
  TA(const char * str):_name(str){
    cout << "constructor is called:" << _name << endl;
    cout << tb._name <<endl;
  };

  virtual ~TA(){
    cout << "destructor is called:" << _name << endl;
  };

  static TB<T> tb;
};

template<typename T>
  TB<T> TA<T>::tb("static-tb");
TA<int> ta("global-ta");

int main(int argc,char ** argv){
  cout << "program started." << endl;
  cout << "program stopped." << endl;
  return 0;
}

/////////////////////////
//  OUTPUT:
constructor is called:global-ta
// yes, only such a single line.
Run Code Online (Sandbox Code Playgroud)

如果我把ta的定义放在main()中,如下所示,它可以工作.

int main(int argc,char ** argv){
  cout << "program started." << endl;
  TA<int> ta("local-ta");
  cout << "program stopped." << endl;
  return 0;
}

/////////////////////
//  OUTPUT:
constructor is called:static-tb
program started.
constructor is called:local-ta
static-tb
program stopped.
destructor is called:local-ta
destructor is called:static-tb
// end of output
Run Code Online (Sandbox Code Playgroud)

A.S*_*S.H 9

在第一个场景中,您的程序在主要启动之前崩溃了.它在构造函数内部崩溃,ta因为它访问了tb尚未构造的内容.这是静态初始化订单Fiasco的一种形式

第二种情况是成功的,因为ta在里面main,并且保证了tb之前构建的非本地的ta.

问题是,为什么在第一个场景中,即使ta在之前构建并且在同一个编译单元中定义,之前已定义?tbtbtatbta

知道这tb是一个模板静态数据成员,cppreference的引用适用:

动态初始化

完成所有静态初始化后,在以下情况下会发生非局部变量的动态初始化:

1)无序动态初始化,仅适用于(静态/线程局部)变量模板和(自C++ 11以来)类模板静态数据成员,这些成员未明确专门化.这种静态变量的初始化对于所有其他动态初始化是不确定的顺序,除非程序在变量初始化之前启动一个线程,在这种情况下它的初始化是未排序的(因为C++ 17).对于所有其他动态初始化,此类线程局部变量的初始化未被排序.

所以这里没有保证顺序!由于ta是具有显式模板特化的静态变量,因此允许编译器在之前初始化它tb.

同一页的另一个引用说:

早期动态初始化

如果以下条件都为真,则允许编译器初始化动态初始化变量作为静态初始化的一部分(基本上,在编译时):

1)初始化的动态版本在初始化之前不会更改命名空间作用域的任何其他对象的值

2)初始化的静态版本在初始化变量中产生与动态初始化产生的值相同的值,如果所有不需要静态初始化的变量都是动态初始化的.由于上面的规则,如果某个对象o1的初始化引用了命名空间范围对象o2,这可能需要动态初始化,但稍后在同一个转换单元中定义,则未指定所使用的o2的值是否为值完全初始化的o2(因为编译器将o2的初始化提升为编译时)或者o2的值仅为零初始化.

编译器已根据这些规则决定推动ta之前的初始化tb.它是否被提升为静态初始化并不清楚,但无论如何,由于第一个引用和第一个引用的推广规则,当涉及到变量模板和静态模板成员时,似乎很清楚初始化序列不能得到保证.第二个报价.

为确保tb在使用之前对其进行初始化,最简单的方法是将其放在包装函数中.我认为在处理静态模板成员时,这应该是某种经验法则:

template<typename T>
class TA{
    //...
    static TB<T>& getTB();
};

template<typename T>
TB<T>& TA<T>::getTB()
{ static TB<T> tb("static-tb");
  return tb;
}
Run Code Online (Sandbox Code Playgroud)