C ++静态成员初始化(模板乐趣里面)

对于静态成员初始化,我使用嵌套的帮助器结构,对于非模板类非常适用。 但是,如果封闭类由模板参数化,则嵌套初始化类不会实例化,如果辅助对象在主代码中未被访问。 为了说明,一个简单的例子(在我的情况下,我需要初始化一个向量)。

#include <string>
#include <iostream>

struct A
{
    struct InitHelper
    {
        InitHelper()
        {
            A::mA = "Hello, I'm A.";
        }
    };
    static std::string mA;
    static InitHelper mInit;

    static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;


template<class T>
struct B
{
    struct InitHelper
    {
        InitHelper()
        {
            B<T>::mB = "Hello, I'm B."; // [3]
        }
    };
    static std::string mB;
    static InitHelper mInit;

    static const std::string& getB() { return mB; }
    static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;


int main(int argc, char* argv[])
{
    std::cout << "A = " << A::getA() << std::endl;

//    std::cout << "B = " << B<int>::getB() << std::endl; // [1]
//    B<int>::getHelper();    // [2]
}

用g ++ 4.4.1:

  • [1]和[2]评论道:

    A = Hello, I'm A.

    按预期工作

  • [1]未评论:

    A = Hello, I'm A.
    B = 

    我期望,InitHelper初始化mB

  • [1]和[2]未注释:
    A = Hello, I'm A.
    B = Hello, I'm B.
    按预期工作
  • [1]评论说,[2]未注释:
    Segfault在静态初始化阶段[3]
  • 因此,我的问题:这是一个编译器错误还是坐在监视器和椅子之间的错误? 如果后者是这种情况:是否有一个优雅的解决方案(即没有明确调用静态初始化方法)?

    更新I:
    这似乎是一种理想的行为(如ISO / IEC C ++ 2003标准14.7.1中所定义):

    除非类模板或成员模板的成员已被明确实例化或显式专用,否则当要求成员定义存在的上下文中引用专用化时,会隐式地实例化成员的专业化; 特别是静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身以需要静态数据成员定义存在的方式使用。


    这在前段时间讨论过usenet,而我试图回答关于stackoverflow的另一个问题:静态数据成员的实例化点。 我认为减少测试用例是值得的,并且单独考虑每个场景,所以让我们先看一下更一般的场景:


    struct C { C(int n) { printf("%dn", n); } };
    
    template<int N>
    struct A {
      static C c;
    }; 
    
    template<int N>
    C A<N>::c(N); 
    
    A<1> a; // implicit instantiation of A<1> and 2
    A<2> b;
    

    你有一个静态数据成员模板的定义。 由于14.7.1原因,这还没有创建任何数据成员:

    “......尤其是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身以需要静态数据成员定义存在的方式使用。”

    根据定义该单词的一个定义规则(在3.2/2 ),该实体被“使用”时,需要定义一些东西(=实体)。 特别是,如果所有引用来自未实例化的模板,模板或sizeof表达式的成员或类似的东西,它们不“使用”实体(因为它们要么没有进行潜在的评估,要么只是不存在函数/成员函数本身使用),这样一个静态数据成员没有实例化。

    一个由14.7.1/7实现的隐式实例化实例化静态数据成员的声明 - 也就是说,它将实例化处理该声明所需的任何模板。 但是,它不会实例化定义 - 也就是说,初始化程序没有实例化,并且该静态数据成员类型的构造函数没有隐式定义(标记为“已使用”)。

    这一切都意味着,上面的代码将不会输出任何内容。 让我们现在引起静态数据成员的隐式实例化。

    int main() { 
      A<1>::c; // reference them
      A<2>::c; 
    }
    

    这会导致两个静态数据成员存在,但问题是 - 初始化的顺序如何? 在简单的阅读中,人们可能会认为3.6.2/1适用,其中说(我强调):

    “在命名空间范围内在同一个翻译单元中定义并动态初始化的具有静态存储持续时间的对象应按其定义出现在翻译单元中的顺序进行初始化。”

    现在正如usenet文章中所述并在此缺陷报告中所述,这些静态数据成员未在翻译单元中定义,但它们在实例化单元中实例化,如2.1/1

    检查每个翻译的翻译单元以产生所需实例的列表。 [注:这可能包括明确要求的实例(14.7.2)。 ]找到所需模板的定义。 它是实现定义的,包含这些定义的翻译单元的来源是否需要可用。 [注意:一个实现可以将足够的信息编码到翻译的翻译单元中,以确保这里不需要源。 ]执行所有必需的实例以生成实例化单元。 [注意:这些翻译单位与翻译单位类似,但不包含对未经实例化的模板的引用,也不包含模板定义。 ]如果任何实例化失败,该程序不合格。

    这种成员的实例化点也并不重要,因为这样的实例化点就是实例化与其翻译单元之间的上下文链接 - 它定义了可见的声明(如14.6.4.1 ,并且每个那些实例化必须给出实例化的意义,如3.2/5 ,最后一个项目符号中的一个定义规则中所指定的那样)。

    如果我们想要有序的初始化,我们必须安排,所以我们不要混淆实例化,而是使用显式声明 - 这是显式特化的区域,因为它们与正常声明并没有太大的不同。 实际上,C ++ 0x将其3.6.2措辞改为如下:

    具有静态存储持续时间的非本地对象的动态初始化是有序的或无序的。 显式专用类模板静态数据成员的定义已经有序初始化。 其他类模板静态数据成员(即,隐含或显式实例化的特化)具有无序初始化。


    这对您的代码意味着:

  • [1][2]评论道:不存在对静态数据成员的引用,所以它们的定义(也不是它们的声明,因为不需要实例化B<int> )没有实例化。 没有副作用发生。
  • [1] B<int>::getB() :使用B<int>::getB() ,它本身使用B<int>::mB ,这要求存在静态成员。 该字符串在main之前被初始化(任何情况下在该语句之前,作为初始化非本地对象的一部分)。 没有使用B<int>::mInit ,所以它没有实例化,所以也没有创建B<int>::InitHelper对象,这使得它的构造函数不被使用,反过来也不会给B<int>::mB :你将只输出一个空字符串。
  • [1][2]注释:这对你有效,是运气(或相反:))。 如上所述,不需要特定的初始化调用顺序。 它可能在VC ++上工作,在GCC上失败并在clang上工作。 我们不知道。
  • [1]评论说, [2]注释:同样的问题 - 同样使用静态数据成员: B<int>::mInitB<int>::getHelper ,并且B<int>::mInit的实例化B<int>::mInit会导致它的构造函数被实例化,它将使用B<int>::mB - 但是对于你的编译器,这个特定的运行顺序是不同的(未指定的行为不需要在不同的运行中保持一致):它初始化B<int>::mInit第一,这将一个还未构造的字符串对象上操作。

  • 问题是你给静态成员变量的定义也是模板。

    template<class T>
    std::string B<T>::mB;
    template<class T>
    typename B<T>::InitHelper B<T>::mInit;
    

    在编译期间,这实际上没有定义任何内容,因为T不知道。 它类似于类声明或模板定义,编译器在看到它时不会生成代码或保留存储。

    当您使用模板类时,定义会稍后隐式发生。 因为在segfaulting的情况下,你不使用B <int> :: mInit,它永远不会被创建。

    解决方案将明确定义所需的成员(不初始化它):将某处的源文件放入一个

    template<>
    typename B<int>::InitHelper B<int>::mInit;
    

    这与明确定义模板类的方式基本相同。


  • [1]未注释的情况:没关系。 static InitHelper B<int>::mInit不存在。 如果模板类(struct)的成员未被使用,则不会被编译。

  • [1]和[2]未注释的情况:没关系。 B<int>::getHelper()使用static InitHelper B<int>::mInitmInit存在。

  • [1]评论说,[2]未注释:它适用于VS2008中的我。

  • 链接地址: http://www.djcxy.com/p/96349.html

    上一篇: C++ Static member initialization (template fun inside)

    下一篇: static constructors in C++? I need to initialize private static objects