【问题标题】:Why is an inline static variable of a template class not initialized?为什么模板类的内联静态变量没有初始化?
【发布时间】:2020-03-21 22:44:43
【问题描述】:

最小的例子和问题

在这个例子中,一个非模板类的内联静态成员变量c2在类成员被实例化时被初始化,而模板类的成员变量c1没有被初始化。有什么区别?为什么c1 没有被初始化,除非我通过获取它的地址来强制它,并且c2 被无条件地初始化?

struct C1 {                        
    C1() { std::cerr << "C1()\n"; }
};                                 


struct C2 {                        
    C2() { std::cerr << "C2()\n"; }
};                                 

template<typename T>               
struct Template {                  
    inline static C1 c1;           
};                                 

struct Nontemplate {                
    inline static C2 c2;           
};                                 

int main() {                       
    Template<int> a;               
    Nontemplate b;                  
    (void)a;                       
    (void)b;                       
}
// Output:
C2()                                  

上下文和推理

以下是最小示例的一些上下文。我有从Template&lt;something&gt; 继承的Nontemplate 类,c2 的构造函数依赖于c1。我希望在c2 之前创建c1;然而,事实并非如此。

template<typename T>                                                     
struct Template {                                                        
    inline static C1 c1;                                                 
};                                                                       

struct Nontemplate : public Template<int> {                               
    struct C2 {                                                          
        C2() {                                                           
            std::cerr << "Do something with Nontemplate::C1\n";          
            std::cerr << "&Nontemplate::c1 = " << &Nontemplate::c1 << "\n";
        }                                                                
    };                                                                   
    inline static C2 c2;                                                 
};                                                                       

int main() {                                                             
    Nontemplate b;                                                        
    (void)b;                                                             
}                                                                        
// Output:
Do something with Nontemplate::C1
&Nontemplate::c1 = 0x600ea8       
C1()                             

代码是使用带有-std=c++17 标志的g++ 7.2 编译的。 -O0-O2 都给出相同的结果。

【问题讨论】:

  • 什么编译器?实现之间是否一致? (不同的静态初始化在几个 Clang / GCC / ICC 版本之间是“古怪的”,不管标准读数可能表明什么......)
  • @user2864740 g++ 7.2。我更新了问题。
  • 在第一个例子中,c1 没有被创建,因为你没有在任何地方使用它。 (同样适用于模板类的成员函数。)有ways 诱使编译器创建c1,但正如您注意到的,即使构造了c1,它也可能在@987654341 之前构造,也可能不构造@,详见this。要强制执行特定的构造顺序,请将c1 移动到某个函数(并使其保持静态,然后在第一次到达声明时构造)。
  • @HolyBlackCat 那么,模板类的静态变量属于动态初始化,非模板类的静态变量属于静态初始化?这解释了一切。看来我不应该这样解决问题。您能否将其发布为答案,以便我接受?
  • @IvanSmirnov 不,静态初始化本质上是针对具有constexpr 构造函数/初始化程序的变量。否则,非模板类的静态变量被初始化为动态初始化的一部分(“有序动态初始化”)。 “只是一个评论 - 我在帖子中注意到了这一点” 是的,抱歉,我的评论有点仓促。 “你能把它作为答案发布吗?”我现在真的没有时间写一个完整的答案。 ://

标签: c++ templates inheritance inline


【解决方案1】:

类模板的隐式实例化只会导致它包含的声明的实例化。定义通常只有在需要定义存在的上下文中使用时才会被实例化。

只要您不以需要其定义存在的方式使用Template&lt;int&gt;::c1(即通过 odr 使用它),那么它就不会被定义。

正如您所提到的,使用变量的一种方式是获取其地址。

即使你强制实例化变量,也不能保证它会在什么时候被初始化。

C1的构造函数不是constexpr,所以Nontemlate::c1的初始化不能是常量表达式。这意味着您将获得Template&lt;int&gt;::c1 的动态初始化。作为模板特化一部分的全局静态变量的动态初始化是无序的,这意味着相对于全局静态变量的任何其他动态初始化,无法保证它们将以什么顺序发生。

同样Nontemlate::c2 不是由常量表达式初始化的,因此也是动态初始化的。尽管Nontemlate::c2 具有部分有序的动态初始化(作为inline 变量而不是模板特化的一部分),但如上所述,它相对于Template&lt;int&gt;::c1 的顺序仍然不确定。

在输入main 之前初始化Template&lt;int&gt;::c1Nontemlate::c2 也不是严格要求。初始化是否推迟到稍后,但在相应变量的第一次 odr 使用之前,由实现定义。不过,我认为这种延迟主要用于运行时动态库加载。

避免全局静态存储持续时间变量排序问题的常用方法是使用返回对局部静态变量的引用的函数,即:

template<typename T>               
struct Template {                  
    static auto& c1() {
        static C1 instance; 
        return instance;
    }          
};

虽然这在经常调用时可能会对性能产生影响,因为每次都必须检查本地静态的构造。

或者,如果可以将初始化器设为常量表达式,则使变量constexpr 应该保证常量初始化,这意味着根本不会发生动态初始化并且不会出现排序问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-09-17
    • 2017-05-26
    • 2021-08-07
    • 2017-08-16
    • 1970-01-01
    • 2017-03-24
    • 1970-01-01
    相关资源
    最近更新 更多