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

发布于 2024-08-13 07:32:47 字数 1825 浏览 3 评论 0原文

对于静态成员初始化,我使用嵌套辅助结构,它对于非模板化类效果很好。 但是,如果外围类由模板参数化,并且在主代码中未访问辅助对象,则不会实例化嵌套初始化类。 为了便于说明,一个简化的示例(在我的例子中,我需要初始化一个向量)。

#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 = 你好,我是A。

    按预期工作

  • [1]未注释:

    A = 你好,我是A。
    B =   
    
    

    我希望 InitHelper 初始化 mB

  • [1] 和 [2] 未注释:
    A = 你好,我是A。
    B = 你好,我是B。
    按预期工作
  • [1] 已注释,[2] 未注释:
    [3] 中静态初始化阶段的段错误

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

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

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

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:

  • [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]

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

懒的傷心 2024-08-20 07:32:47

前段时间在 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;

您有静态数据成员模板的定义。由于 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< 中指定的) /code>,并且每个实例化点都必须赋予实例化相同的含义,如 3.2/5 的一个定义规则(最后一个项目符号)中指定的那样。

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

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


这对您的代码意味着:

  • [1][2] 注释:没有引用静态数据成员存在,因此它们的定义(也不是它们的声明,因为不需要实例化 B)不会被实例化。没有副作用发生。
  • [1] 未注释:使用 B::getB(),它本身使用 B: :mB,这要求静态成员存在。该字符串在 main 之前初始化(无论如何在该语句之前,作为初始化非本地对象的一部分)。没有任何内容使用 B::mInit,因此它不会被实例化,因此不会创建 B::InitHelper 的对象,这使得它的构造函数不被创建正在使用,这反过来永远不会为 B::mB 分配某些内容:您只会输出一个空字符串。
  • [1][2] 未注释:这对你有用就是运气(或者相反:) )。如上所述,对于初始化调用的特定顺序没有要求。它可能在 VC++ 上工作,在 GCC 上失败,在 clang 上工作。我们不知道。
  • [1] 已评论,[2] 未评论:同样的问题 - 再次出现,两者 静态数据成员被使用B::mInitB::getHelper使用,并且实例化B::mInit 将导致其构造函数被实例化,它将使用 B::mB - 但对于您的编译器来说,顺序是不同的此特定运行(未指定的行为不需要在不同运行之间保持一致):它首先初始化 B::mInit,它将对尚未构造的字符串对象进行操作。

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", 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:

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

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

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

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:

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.

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

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] 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.
っ左 2024-08-20 07:32:47

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

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

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

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

解决方案是显式定义所需的成员(而不对其进行初始化): 将源文件 a 放在某处

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

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

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.

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.

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.

嘿哥们儿 2024-08-20 07:32:47
  • [1]未注释的情况:
    没关系。 static InitHelper B::mInit 不存在。如果不使用模板类(结构)的成员,则不会编译。

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

  • [1]已评论,[2]未评论:
    它在 VS2008 中适用于我。

  • [1] uncommented case:
    It is ok. static InitHelper B<int>::mInit does 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>::mInit and mInit exists.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文