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

查看:135
本文介绍了C ++静态成员初始化(模板乐趣)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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

  #include< string> 
#include< iostream>

struct A
{
struct InitHelper
{
InitHelper()
{
A :: mA =Hello,我是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 


  • [1]取消注释:



     A =您好,我是A. 
    B =

    我希望InitHelper初始化mB


  • [1]和[2]取消注释:

     A = Hello ,我是A. 
    B =你好,我是B。



    按预期工作


  • 已注释,[2]取消注释:

    Segfault在[3]的静态初始化阶段



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



非常感谢您的任何建议/ comments。



更新I:

这似乎是一个所需的行为C ++ 2003标准,14.7.1):


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



解决方案

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




  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&一个; // A 1的隐式实例化和2
A 2 b;

您有静态数据成员模板的定义。这尚未创建任何数据成员,因为 14.7.1


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


当该实体被使用时,根据定义该字的一个定义规则( 3.2 / 2 )。特别是,如果所有引用都来自未实例化的模板,模板的成员或 sizeof 表达式或类似的东西,不使用实体(因为它们不是潜在地评估它,或者它们仅仅不存在作为其本身使用的函数/成员函数),这样的静态数据成员不被实例化。



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



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

  int main(){
A& //引用它们
A< 2> :: c;
}

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



< blockquote>

在同一个翻译单元中在命名空间范围中定义并且动态初始化的静态存储持续时间的对象应按照它们在翻译单元中出现的顺序初始化。


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


以产生所需实例化的列表。 [注意:这可能包括明确请求的实例化(14.7.2)。 ]所需模板的定义位于。它是由实现定义的,包含这些定义的翻译单元的源是否需要可用。 [注意:实现可以将足够的信息编码到翻译的翻译单元中,以便确保这里不需要源。 ]执行所有所需的实例化以产生实例化单元。 [注意:这些类似于翻译的翻译单位,但不包含对未实例化模板的引用,也不包含模板定义。 ]


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



如果我们想要排序初始化,我们必须安排,所以我们不要混淆实例化,但是使用显式声明 - 这是显式专门化的领域,因为没有真正不同于正常的声明。事实上,C ++ 0x将 3.6.2 的措辞改为以下内容:


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



/ p>

这意味着您的代码:




  • [1] [2] 已注释:存在,因此它们的定义(也不是它们的声明,因为不需要实例化 B )不被实例化。 c 取消注释: B< int> ; :: getB(),它本身使用 B< int> :: mB ,这要求静态成员存在。字符串在main之前被初始化(在该语句之前的任何情况下,作为初始化非局部对象的一部分)。没有使用 B< int> :: mInit ,所以它不实例化,因此没有对象 B 永远被创建,这使得它的构造函数不被使用,而这又不会为<$​​ c $ c> B 赋值:串。

  • [1] [2] c $ c> uncommented:这对你工作是运气(或相反:))。如上所述,不需要初始化调用的特定顺序。它可能在VC ++上工作,在GCC上失败,并在clang上工作。我们不知道。

  • [1] 已评论, :使用: ; int> :: mInit B 使用,并且实例化 B 将导致其构造函数被实例化,这将使用 B - 但是对于你的编译器,在这个特定的运行中是不同的(未指定的行为不需要在不同的运行之间是一致的):它首先初始化 B -yet构造的字符串对象。



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

Thank you very much for any suggestions/comments.

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.

解决方案

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.

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

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