2009-11-30 70 views
37

对于静态成员初始化,我使用嵌套帮助程序结构,对于非模板类可以正常工作。 但是,如果封闭类由模板参数化,则嵌套的初始化类未实例化,如果辅助对象未在主代码中访问。 为了说明,一个简化的例子(在我的情况下,我需要初始化一个向量)。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] 
} 

随着克++ 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]未注释:
    段错误在静态初始化阶段在[3]

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

更新I:
这似乎是一个期望的行为(如在ISO/IEC C++ 2003标准,14.7.1定义):

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

+0

Visual Studio 2008中具有相同的行为(在静态初始化-segfault) – 2009-11-30 11:08:27

+0

你为什么不只是写的std ::串B :: MB = “你好,我是B”? – 2009-11-30 11:11:27

+0

好吧,我看到在实际情况下,您需要无损伤媒介,抱歉。 – 2009-11-30 11:12:09

回答

36

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

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

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

14.7.1/7的一个隐式实例化实例化了静态数据成员的声明 - 也就是说,它将实例化处理该声明所需的任何模板。但是,它不会实例化定义 - 也就是说,初始化程序没有实例化,并且该静态数据成员类型的构造函数没有隐式定义(标记为“已使用”)。

这一切都意味着,上面的代码将不会输出任何东西。让我们现在引起静态数据成员的隐式实例化。

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

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

“对象在同一翻译单元在命名空间范围内定义静态存储期限和动态初始化应初始化其定义出现在翻译单元中的顺序。“

现在在Usenet的帖子中说,并解释in this defect report,这些静态数据成员不会在翻译单元中定义的,但它们被实例化在实例化单元,截至2.1/1解释说:

检查每个翻译过的翻译单元以产生一个需要的实例列表[注意:这可能包括已明确请求的实例(14.7.2)。]定义所需模板的定义。包含这些定义的翻译单元的来源是r等于可用。 [注意:一个实现可以将足够的信息编码到翻译的翻译单元中,以确保这里不需要源。 ]执行所有必需的实例以生成实例化单元。 [注意:这些翻译单位与翻译单位类似,但不包含对未经实例化的模板的引用,也不包含模板定义。 ]如果任何实例化失败,该程序不合格。

实例化这样的成员的点也并不重要,因为实例化的这样一个点是一个实例化和它的翻译单元之间的上下文链接 - 它定义可见(截至14.6.4.1所规定的声明,并且这些实例化点中的每一个必须给出具有相同含义的实例化,如在3.2/5的最后一个项目符号中的一个定义规则中所指定的)。如果我们想要有序的初始化,我们必须安排,所以我们不要混淆实例化,而是使用显式声明 - 这是显式特化的区域,因为它们与正常声明并没有真正的不同。事实上,C++ 0X改变了它的3.6.2措辞为以下:

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


这意味着你的代码,即:

  • [1][2]评论:没有参照静态数据成员存在,所以它们的定义(也没有它们的声明,因为不需要实例化B<int>)未被实例化。没有副作用发生。
  • [1]未注释: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>::mInitB<int>::getHelper使用,并且B<int>::mInit实例化将导致其构造函数中被实例化,这将使用B<int>::mB - 但是对于您的编译器,在此特定运行中顺序不同(未指定的行为不需要在不同的运行中保持一致):首先初始化B<int>::mInit,它将在尚未构建的字符串对象上运行。
2
  • [1]未注释的情况下: 这是确定。 static InitHelper B<int>::mInit不存在。如果模板类(struct)的成员未被使用,则不会被编译。

  • [1]和[2]未注释的案例: 没关系。 B<int>::getHelper()使用static InitHelper B<int>::mInitmInit存在。

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

4

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

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

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

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

一个解决办法是显式定义所需的部件(不对其进行初始化):地方把源文件

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

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

+0

我想,在这个例子中,将初始化放到另一个头文件中并没有什么区别,因为所有东西都在同一个翻译单元中。 – 2009-11-30 11:58:42

+0

当然。那就是为什么我使用了条件式(“would”)。但我不是英语母语的人,所以这是可以误导的。我删了它。 – hirschhornsalz 2009-11-30 15:08:01

+0

那么你现在明白为什么你会得到一个段错误/空的成员,或者它还不清楚? – hirschhornsalz 2009-11-30 15:12:03