其他分享
首页 > 其他分享> > C静态成员初始化(模板内部有趣)

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 = 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]未注释:
Segfault在[3]的静态初始化阶段

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

更新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.

解决方法:

这在一段时间之前在usenet上进行了讨论,而我正在尝试回答有关stackoverflow的另一个问题:Point of Instantiation of Static Data Members.我认为值得减少测试用例,并且考虑每个场景是孤立的,所以让我们首先看一下它:

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,这还没有创建任何数据成员:

“… 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表达式或不使用“实体”的类似事物(因为它们不是可能正在评估它,或者它们只是不存在)本身使用的函数/成员函数),这样的静态数据成员不会被实例化.

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

这一切都意味着,上面的代码还没有输出.现在让我们引起静态数据成员的隐式实例化.

int main() { 
  A<1>::c; // reference them
  A<2>::c; 
}

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

“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帖子中所述并解释了in this defect report,这些静态数据成员未在翻译单元中定义,但它们在实例化单元中实例化,如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.6.4.1所述,并且每个那些实例化点必须赋予实例化相同的含义,如3.2 / 5中的一个定义规则,最后一个子弹中所规定的那样.

如果我们想要有序初始化,我们必须安排所以我们不要乱用实例化,但是使用显式声明 – 这是显式特化的区域,因为它们与普通声明没有什么不同.实际上,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.

这对您的代码意味着:

> [1]和[2]评论:不存在对静态数据成员的引用,因此不实例化它们的定义(也不是它们的声明,因为不需要实例化B< int>).没有副作用发生.
> [1]取消注释:使用B< int> :: getB(),它本身使用B< int> :: mB,这要求静态成员存在.字符串在main之前初始化(在任何情况下,在该语句之前,作为初始化非本地对象的一部分).没有使用B&LT INT&GT :: MINIT,所以它不是实例化,所以没有B&LT的对象; INT&GT :: InitHelper是有史以来,这使得不使用它的构造,这反过来会永远分配的东西给B&LT INT&GT ; :: mB:您将只输出一个空字符串.
> [1]和[2]没有注释:这对你有用(或者相反:)).如上所述,不需要特定的初始化调用顺序.它可能适用于VC,在GCC上失败并且可以处理clang.我们不知道.
> [1]评论,[2]未注释:同样的问题 – 再次,两个静态数据成员被使用:B&LT INT&GT :: MINIT使用由B&LT INT&GT ::调用getHelper,和B&LT的实例; INT计算值: :mInit将导致其构造函数被实例化,它将使用B< int> :: mB – 但对于您的编译器,此特定运行中的顺序是不同的(未指定的行为不需要在不同的运行之间保持一致):它初始化B< int> :: mInit首先,它将对尚未构造的字符串对象进行操作.

标签:c,templates,initialization,static-members
来源: https://codeday.me/bug/20190916/1806686.html