C++静态成员初始化(模板乐趣里面) [英] C++ Static member initialization (template fun inside)

查看:46
本文介绍了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]
  • 静态初始化阶段的段错误

    Works as intended

  • [1] commented, [2] uncommented:
    Segfault in the static initialization stage at [3]
  • 因此我的问题是:这是编译器错误还是显示器和椅子之间的错误?如果是后者:是否有优雅的解决方案(即不显式调用静态初始化方法)?

    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 帖子中所说并在 中解释这个缺陷报告,这些静态数据成员没有在翻译单元中定义,而是在实例化单元中实例化,如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)没有实例化.不会产生副作用.
    • [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.

    这篇关于C++静态成员初始化(模板乐趣里面)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆