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

C++ template: The static member in a global object is not initialized

我有一段简单的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.

如果我像下面这样将 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

在第一种情况下,您的程序在 main 启动之前崩溃了。它在 ta 的构造函数内部崩溃,因为它访问了尚未构造的 tb 。这是 静态初始化顺序 Fiasco

的一种形式

第二种情况是成功的,因为 tamain 里面,这保证了非局部的 tb 是在 ta 之前构造的。

问题是,为什么在第一种情况下,tatb之前构造,即使tbta是在同一个编译单元中定义的,与tbta?

之前定义

知道tb是模板静态数据成员,this quote from cppreference applies:

Dynamic initialization

After all static initialization is completed, dynamic initialization of non-local variables occurs in the following situations:

1) Unordered dynamic initialization, which applies only to (static/thread-local) variable templates and (since C++11) class template static data members that aren't explicitly specialized. Initialization of such static variables is indeterminately sequenced with respect to all other dynamic initialization except if the program starts a thread before a variable is initialized, in which case its initialization is unsequenced (since C++17). Initialization of such thread-local variables is unsequenced with respect to all other dynamic initialization.

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

同一页的另一引述说:

Early dynamic initialization

The compilers are allowed to initialize dynamically-initialized variables as part of static initialization (essentially, at compile time), if the following conditions are both true:

1) the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization

2) the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically. Because of the rule above, if initialization of some object o1 refers to an namespace-scope object o2, which potentially requires dynamic initialization, but is defined later in the same translation unit, it is unspecified whether the value of o2 used will be the value of the fully initialized o2 (because the compiler promoted initialization of o2 to compile time) or will be the value of o2 merely zero-initialized.

编译器根据这些规则决定将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;
}