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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1819131/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:14:53  来源:igfitidea点击:

C++ Static member initialization (template fun inside)

c++templatesinitializationstatic-members

提问by Mr. Mr.

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]
}

With g++ 4.4.1:

使用 g++ 4.4.1:

  • [1] and [2] commented:

    A = Hello, I'm A.

    Works as intended

  • [1] uncommented:

    A = Hello, I'm A.
    B = 

    I would expect, that the InitHelper initializes mB

  • [1] and [2] uncommented:
    A = Hello, I'm A.
    B = Hello, I'm B.
    Works as intended
  • [1] commented, [2] uncommented:
    Segfault in the static initialization stage at [3]
  • [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] 未评论:
    [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)?

因此我的问题是:这是一个编译器错误还是位于显示器和椅子之间的错误?如果是后者:是否有一个优雅的解决方案(即没有显式调用静态初始化方法)?

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

更新 I:
这似乎是一种理想的行为(如 ISO/IEC C++ 2003 标准 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.

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

采纳答案by Johannes Schaub - litb

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:

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



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

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

您已经定义了静态数据成员模板。这还没有创建任何数据成员,因为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."

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

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 sizeofexpressions 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.

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

An implicit instantiation by 14.7.1/7instantiates 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).

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

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; 
}

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/1applies, which says (emphasis by me):

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

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

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

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:

现在正如 usenet 帖子中所述并在此缺陷报告中解释的那样,这些静态数据成员未在翻译单元中定义,而是在实例化单元实例化,如下所述2.1/1

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.7.2)。] 所需模板的定义位于。是否需要包含这些定义的翻译单元的源是实现定义的。[注意:实现可以将足够的信息编码到翻译的翻译单元中,以确保此处不需要源。] 执行所有必需的实例化以生成实例化单元。[注意:这些类似于翻译的翻译单元,但不包含对未实例化模板的引用,也没有模板定义。] 如果任何实例化失败,程序就是格式错误的。

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).

这样的成员的实例化点也并不重要,因为实例化的这样一个点是一个实例化和它的翻译单元之间的上下文链接-它定义了在可见的(如在规定的申报14.6.4.1,每个这些点实例化必须赋予实例化相同的含义,如在3.2/5最后一个项目符号处的一个定义规则中所指定的)。

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.2to the following:

如果我们想要有序的初始化,我们必须安排这样我们不会混淆实例化,而是使用显式声明 - 这是显式专业化的领域,因为它们与普通声明并没有真正不同。实际上,C++0x 将其措辞更改3.6.2为以下内容:

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]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>::InitHelperis 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, bothstatic data members are used: B<int>::mInitis used by B<int>::getHelper, and the instantiation of B<int>::mInitwill 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>::mInitfirst, which will operate on a not-yet-constructed string object.
  • [1][2]评论说:不存在对静态数据成员的引用,因此它们的定义(也不是它们的声明,因为不需要实例化B<int>)没有实例化。不会发生副作用。
  • [1]uncommented: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>::mInit被 使用B<int>::getHelper,并且 的实例化B<int>::mInit将导致其构造函数被实例化,这将使用B<int>::mB- 但对于您的编译器,此特定运行中的顺序不同(未指定的行为不需要在不同的运行之间保持一致):它B<int>::mInit首先初始化,它将对尚未构造的字符串对象进行操作。

回答by Gunther Piez

The problem is that the defintions you give for the static member variables are templates too.

问题是您为静态成员变量提供的定义也是模板。

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

During compilation, this defines actually nothing, since T is not known. It is something like a class declaration or a template definition, the compiler does not generate code or reserve storage when it sees it.

在编译期间,这实际上什么也没有定义,因为 T 是未知的。它类似于类声明或模板定义,编译器在看到它时不会生成代码或保留存储空间。

The definition happens implicitly later, when you use the template class. Because in the segfaulting case you don't use B<int>::mInit, it is never created.

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

A solution would be explictly defining the needed member (without initializing it): Put somewhere source file a

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

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

This works basically the same way as explictly defining a template class.

这与显式定义模板类的工作方式基本相同。

回答by Alexey Malistov

  • [1] uncommented case: It is ok. static InitHelper B<int>::mInitdoes not exist. If member of the template class (struct) is not used it does not compiled.

  • [1] and [2] uncommented case: It is ok. B<int>::getHelper()use static InitHelper B<int>::mInitand mInitexists.

  • [1] commented, [2] uncommented: it works for me in VS2008.

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

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

  • [1] 评论,[2] 未评论:它在 VS2008 中对我有用。