C ++静态成员初始化(内部模板有趣)

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


#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]未评论:

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

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


更新I:

这似乎是理想的行为(在ISO / IEC C ++ 2003标准14.7.1中定义):


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


哔哔one
浏览 330回答 3
3回答

子衿沉夜

前段时间在usenet上对此进行了讨论,而当时我正试图回答关于stackoverflow的另一个问题:静态数据成员的实例化点。我认为减少测试用例,并单独考虑每种情况是值得的,所以让我们首先更一般地看一下它:struct C { C(int n) { printf("%d\n", n); } };template<int N>struct A {&nbsp; static C c;};&nbsp;template<int N>C A<N>::c(N);&nbsp;A<1> a; // implicit instantiation of A<1> and 2A<2> b;您已定义了静态数据成员模板。由于以下原因,此操作尚未创建任何数据成员14.7.1:“ ...尤其是,除非以某种方式要求使用静态数据成员的定义才能使用静态数据成员,否则不会发生静态数据成员的初始化(以及任何相关的副作用)。”根据定义该词的一种定义规则(在处3.2/2),当“使用”某个实体时,需要定义某种事物(=实体)。特别是,如果所有引用均来自未实例化的模板,模板的成员或sizeof表达式或不“使用”实体的类似事物(因为它们可能没有对实体进行评估,或者它们还不作为函数存在) / member函数本身),则不会实例化此类静态数据成员。隐式实例化通过14.7.1/7实例化静态数据成员的声明-也就是说,它将实例化处理该声明所需的任何模板。但是,它不会实例化定义-也就是说,不会实例化初始化程序,并且不会隐式定义该静态数据成员类型的构造函数(标记为已使用)。这一切都意味着,以上代码将不会输出任何内容。现在让我们引起静态数据成员的隐式实例化。int main() {&nbsp;&nbsp; A<1>::c; // reference them&nbsp; A<2>::c;&nbsp;}这将导致两个静态数据成员存在,但问题是-初始化的顺序如何?简单阅读一下,可能会认为这3.6.2/1适用,(我强调):“具有在同一翻译单元中的名称空间范围内定义并动态初始化的具有静态存储持续时间的对象,应按照其定义在翻译单元中出现的顺序进行初始化。”现在,如usenet帖子中所述,并在此缺陷报告中进行了说明,这些静态数据成员未在转换单元中定义,但是在实例化单元中实例化,如以下所述2.1/1:检查每个翻译的翻译单元,以产生所需实例的列表。[注意:这可能包括已明确请求的实例化(14.7.2)。]找到了所需模板的定义。由实现定义,是否要求包含这些定义的翻译单元的源是否可用。[注意:一个实现可以将足够的信息编码到翻译的翻译单元中,以确保此处不需要源。]执行所有必需的实例化以生成实例化单元。[注意:它们类似于翻译后的翻译单元,但不包含对未实例化模板的引用,也没有模板定义。]如果任何实例化失败,则程序格式错误。这样的成员的实例化点也并不重要,因为这样的实例化点是实例化及其转换单元之间的上下文链接-它定义了可见的声明(如处所指定14.6.4.1,以及的每个点)实例化必须赋予实例化相同的含义,如3.2/5最后一个项目符号的一个定义规则中所指定)。如果我们要进行有序的初始化,则必须进行安排,以免混淆实例化,而是使用显式声明-这是显式专业化的领域,因为它们与普通声明没有真正的不同。实际上,C ++ 0x将其措辞更改3.6.2为以下内容:具有静态存储持续时间的非本地对象的动态初始化是有序的或无序的。显式专门化的类模板静态数据成员的定义已进行了初始化。其他类模板静态数据成员(即,隐式或显式实例化的专长)具有无序初始化。这对您的代码意味着:[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,该操作将在尚未构造的字符串对象上进行。

凤凰求蛊

[1]未注释的情况:可以。static InitHelper B<int>::mInit不存在。如果不使用模板类(struct)的成员,则不会编译。[1]和[2]未注释的情况:可以。B<int>::getHelper()使用static InitHelper B<int>::mInit和mInit存在。[1]评论,[2]未评论:它在VS2008中对我有用。
打开App,查看更多内容
随时随地看视频慕课网APP