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
的一种形式
第二种情况是成功的,因为 ta
在 main
里面,这保证了非局部的 tb
是在 ta
之前构造的。
问题是,为什么在第一种情况下,ta
在tb
之前构造,即使tb
和ta
是在同一个编译单元中定义的,与tb
在 ta
?
之前定义
知道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;
}
我有一段简单的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
第二种情况是成功的,因为 ta
在 main
里面,这保证了非局部的 tb
是在 ta
之前构造的。
问题是,为什么在第一种情况下,ta
在tb
之前构造,即使tb
和ta
是在同一个编译单元中定义的,与tb
在 ta
?
知道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;
}