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

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

For static member initialization I use a nested helper struct, which works fine for non templated classes. However, if the enclosing class is parameterized by a template, the nested initialization class is not instantiated, if the helper object is not accessed in the main code. For illustration, a simplified example (In my case, I need to initialize a vector).

#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:

With g++ 4.4.1:

  • [1]和[2]评论:

  • [1] and [2] commented:

A = Hello, I'm A.

按预期工作

[1] 未注释:

A = Hello, I'm A.
B = 

我希望 InitHelper 初始化 mB

I would expect, that the InitHelper initializes mB

A = Hello, I'm A.
B = Hello, I'm B.

按预期工作

  • [1] 已评论,[2] 未评论:
    [3] 的静态初始化阶段的 Segfault
  • 因此我的问题是:这是编译器错误还是位于监视器和椅子之间的错误?如果是后者:是否有优雅的解决方案(即不显式调用静态初始化方法)?

    Thus my question: Is this a compiler bug or is the bug sitting between the monitor and the chair? And if the latter is the case: Is there an elegant solution (i.e. without explicitly calling a static initialization method)?

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

    Update I:
    This seems to be a desired behavior (as defined in the ISO/IEC C++ 2003 standard, 14.7.1):

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

    Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

    推荐答案

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

    This was discussed on usenet some time ago, while i was trying to answer another question on stackoverflow: Point of Instantiation of Static Data Members. I think it's worth reducing the test-case, and considering each scenario in isolation, so let's look at it more general first:

    struct C { C(int n) { printf("%d
    ", 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:

    You have the definition of a static data member template. This does not yet create any data members, because of 14.7.1:

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

    "... in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist."

    根据定义该词的单一定义规则(在 3.2/2),当使用"该实体时需要对某事物(= 实体)进行定义.特别是,如果所有引用都来自未实例化的模板、模板的成员或 sizeof 表达式或不使用"实体的类似事物(因为它们要么没有潜在地评估它,要么它们只是尚不作为本身使用的函数/成员函数存在),这样的静态数据成员不会被实例化.

    The definition of something (= entity) is needed when that entity is "used", according to the one definition rule which defines that word (at 3.2/2). In particular, if all references are from uninstantiated templates, members of a template or a sizeof expressions or similar things that don't "use" the entity (since they are either not potentially evaluating it, or they just don't exist yet as functions/member functions that are itself used), such a static data member is not instantiated.

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

    An implicit instantiation by 14.7.1/7 instantiates declarations of static data members - that is to say, it will instantiate any template needed to process that declaration. It won't, however, instantiate definitions - that is to say, initializers are not instantiated and constructors of the type of that static data member are not implicitly defined (marked as used).

    也就是说,上面的代码还不会输出任何内容.现在让我们对静态数据成员进行隐式实例化.

    That all means, the above code will output nothing yet. Let's cause implicit instantiations of the static data members now.

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

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

    This will cause the two static data members to exist, but the question is - how is the order of initialization? On a simple read, one might think that 3.6.2/1 applies, which says (emphasis by me):

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

    "Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit."

    现在正如 usenet 帖子中所说并解释了 in在这个缺陷报告中,这些静态数据成员没有在翻译单元中定义,而是在实例化单元中实例化,如2.1/1中所述:

    Now as said in the usenet post and explained in this defect report, these static data members are not defined in a translation unit, but they are instantiated in a instantiation unit, as explained at 2.1/1:

    检查每个翻译的翻译单元以生成所需实例化的列表.[注意:这可能包括已明确请求的实例化(14.7.2).] 所需模板的定义位于.是否需要包含这些定义的翻译单元的源是由实现定义的.[注意:实现可以将足够的信息编码到翻译后的翻译单元中,以确保此处不需要源.] 执行所有必需的实例化以生成实例化单元.[注意:这些类似于翻译的翻译单元,但不包含对未实例化模板的引用和模板定义.] 如果任何实例化失败,则程序是非良构的.

    Each translated translation unit is examined to produce a list of required instantiations. [Note: this may include instantiations which have been explicitly requested (14.7.2). ] The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. [Note: an implementation could encode sufficient information into the translated translation unit so as to ensure the source is not required here. ] All the required instantiations are performed to produce instantiation units. [Note: these are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. ] The program is ill-formed if any instantiation fails.

    这样一个成员的实例化点也并不重要,因为这样一个实例化点是实例化与其翻译单元之间的上下文链接――它定义了可见的声明(如 14.6 中所述.4.1,并且每个实例化点都必须赋予实例化相同的含义,如 3.2/5 的一个定义规则中所指定,最后一个项目符号).

    The Point of Instantiation of such a member also does not really matter, because such a point of instantiation is the context link between an instantiation and its translation units - it defines the declarations that are visible (as specified at 14.6.4.1, and each of those point of instantiations must give instantiations the same meaning, as specified in the one definition rule at 3.2/5, last bullet).

    如果我们想要有序初始化,我们必须进行安排,以免混淆实例化,而是使用显式声明 - 这是显式特化的领域,因为它们与普通声明并没有真正的不同.事实上,C++0x 将其 3.6.2 的措辞改为如下:

    If we want ordered initialization, we have to arrange so we don't mess with instantiations, but with explicit declarations - this is the area of explicit specializations, as these are not really different to normal declarations. In fact, C++0x changed its wording of 3.6.2 to the following:

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

    Dynamic initialization of a non-local object with static storage duration is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization.

    <小时>

    这对您的代码意味着:


    This means to your code, that:

    • [1][2] 评论说:不存在对静态数据成员的引用,所以它们的定义(也不是它们的声明,因为不需要实例化 B<int>)没有实例化.不会产生副作用.
    • [1] 未注释:B<int>::getB() 被使用,它本身使用 B<int>::mB,这要求该静态成员存在.字符串在 main 之前初始化(在任何情况下,在该语句之前,作为初始化非本地对象的一部分).没有使用 B<int>::mInit,所以它没有被实例化,所以 B<int>::InitHelper 的对象永远不会被创建,这使得它的构造函数没有被使用,它又永远不会给 B<int>::mB 赋值:你只会输出一个空字符串.
    • [1][2] 未注释:这对您有用是运气(或对面的 :)).如上所述,初始化调用的特定顺序没有要求.它可能在 VC++ 上工作,在 GCC 上失败并在 clang 上工作.我们不知道.
    • [1] 已评论,[2] 未评论:同样的问题 - 再次,两者静态数据成员被使用:B::mInitB::getHelper使用,并且B<int>::mInit 的实例化将导致其构造函数被实例化,这将使用 B<int>::mB - 但对于您的编译器,顺序在这个特定的运行中是不同的(未指定的行为不需要在不同的运行之间保持一致):它首先初始化 B<int>::mInit ,它将对尚未构造的字符串对象进行操作.
    • [1] and [2] commented: No reference to the static data members exist, so their definitions (and also not their declarations, since there is no need for instantiation of B<int>) are not instantiated. No side effect occurs.
    • [1] uncommented: B<int>::getB() is used, which in itself uses B<int>::mB, which requires that static member to exist. The string is initialized prior to main (at any case before that statement, as part of initializing non-local objects). Nothing uses B<int>::mInit, so it's not instantiated, and so no object of B<int>::InitHelper is ever created, which makes its constructor not being used, which in turn will never assign something to B<int>::mB: You will just output an empty string.
    • [1] and [2] uncommented: That this worked for you is luck (or the opposite :)). There is no requirement for a particular order of initialization calls, as explained above. It might work on VC++, fail on GCC and work on clang. We don't know.
    • [1] commented, [2] uncommented: Same problem - again, both static data members are used: B<int>::mInit is used by B<int>::getHelper, and the instantiation of B<int>::mInit will cause its constructor to be instantiated, which will use B<int>::mB - but for your compiler, the order is different in this particular run (unspecified behavior is not required to be consistent among different runs): It initializes B<int>::mInit first, which will operate on a not-yet-constructed string object.

    相关文章