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]
因此,我的问题是:这是一个编译器错误还是坐在监视器和椅子之间的错误? 如果后者是这种情况:是否有一个优雅的解决scheme(即没有明确调用静态初始化方法)?
非常感谢您的任何build议/意见。
更新I:
这似乎是一个理想的行为(如ISO / IEC C ++ 2003标准14.7.1所定义):
除非类模板或成员模板的成员已被明确实例化或明确专用,否则当需要成员定义存在的上下文中引用专用化时,会隐式地实例化成员的专业化; 特别是静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身以需要静态数据成员的定义存在的方式使用。
这在前段时间在usenet上讨论过,而我正试图回答另一个关于stackoverflow的问题: 静态数据成员的实例化点 。 我认为减lesstesting用例是值得的,并且考虑到每个场景都是孤立的,所以让我们先来看看它:
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;
你有一个静态数据成员模板的定义。 这还没有创build任何数据成员,因为14.7.1
:
“…尤其是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身以需要静态数据成员定义存在的方式使用。
根据定义该单词的定义规则( 3.2/2
),该实体被“使用”时,需要定义一些东西(=实体)。 特别是,如果所有引用都来自未经实例化的模板,模板或sizeof
expression式的成员或类似的东西,而不是“使用”实体(因为它们要么不可能评估它,要么只是不存在函数/成员函数本身使用),这样一个静态数据成员没有实例化。
一个由14.7.1/7
实现的隐式实例化实例化静态数据成员的声明 – 也就是说,它将实例化处理该声明所需的任何模板。 但是,它不会实例化定义 – 也就是说,初始化程序没有实例化,并且该静态数据成员types的构造方法没有被隐式定义(标记为“使用”)。
这一切都意味着,上面的代码将不输出任何东西。 我们现在让静态数据成员的隐式实例化。
int main() { A<1>::c; // reference them A<2>::c; }
这将导致两个静态数据成员存在,但问题是 – 如何初始化的顺序? 在简单的阅读中,人们可能会认为3.6.2/1
适用,其中说(强调我):
“在同一个翻译单元中命名空间范围内定义并dynamic初始化的具有静态存储持续时间的对象应按其定义出现在翻译单元中的顺序进行初始化。
现在正如在usenet文章中所说的,并在这个缺陷报告中解释的,这些静态数据成员没有在翻译单元中定义,而是在实例化单元中被实例化 ,如2.1/1
所解释的:
检查每个翻译的翻译单元以产生所需实例的列表。 [注:这可能包括明确要求的实例化(14.7.2)。 ]所需模板的定义位于。 包含这些定义的翻译单元的来源是否需要可用,是由实施定义的。 [注意:一个实现可以将足够的信息编码到已翻译的翻译单元中,以确保源不是必需的。 ]所有需要的实例都被执行来产生实例化单元。 [注:这些翻译单位与翻译单位类似,但不包含对未经实例化的模板的引用,也不包含模板定义。 ]如果任何实例化失败,程序是不合格的。
这样一个成员的实例化点也并不重要,因为这样一个实例化点是实例化和它的翻译单元之间的上下文链接 – 它定义了可见的声明(如14.6.4.1
所指定的,那些实例化的点必须给出实例化的意义,如在3.2/5
,最后一个项目符号中的一个定义规则中所指定的那样)。
如果我们想要有序的初始化,我们必须安排,所以我们不要混淆实例化,但显式声明 – 这是明确的专业领域,因为这些与正常的声明没有什么不同。 事实上,C ++ 0x将其3.6.2
措辞改为:
具有静态存储持续时间的非本地对象的dynamic初始化是有序的或无序的。 显式专用类模板静态数据成员的定义已经有序初始化。 其他类模板静态数据成员(即,隐式或显式实例化的特化)具有无序的初始化。
这对您的代码意味着:
-
[1]
和[2]
评论说:没有引用静态数据成员存在,所以它们的定义(也不是它们的声明,因为不需要实例化B<int>
)没有实例化。 没有副作用发生。 -
[1]
B<int>::getB()
:使用B<int>::getB()
,它本身使用B<int>::mB
,这要求静态成员存在。 该string在main之前被初始化(任何情况下在该语句之前,作为初始化非本地对象的一部分)。 没有使用B<int>::mInit
,所以它没有被实例化,所以也没有创buildB<int>::InitHelper
对象,这使得它的构造函数不被使用,而这又不会使用B<int>::mB
:你将只输出一个空string。 -
[1]
和[2]
uncommented:这对你有效的是运气(或相反:))。 如上所述,不要求初始化调用的特定顺序。 它可能在VC ++上工作,在GCC上失败并在叮当中工作。 我们不知道。 -
[1]
注释:[2]
注释:同样的问题 – 同样使用静态数据成员:B<int>::mInit
被B<int>::getHelper
使用,B<int>::getHelper
的实例化B<int>::mInit
将会使它的构造函数被实例化,它将使用B<int>::mB
– 但是对于你的编译器,这个特定的运行顺序是不同的(未指定的行为不需要在不同的运行之间保持一致):它初始化B<int>::mInit
首先,它将在尚未构造的string对象上运行。
问题是你给静态成员variables的定义也是模板。
template<class T> std::string B<T>::mB; template<class T> typename B<T>::InitHelper B<T>::mInit;
在编译期间,这实际上没有定义,因为T是未知的。 它类似于类声明或模板定义,编译器在看到它时不会生成代码或保留存储空间。
当您使用模板类时,定义会稍后隐式发生。 因为在segfaulting情况下,你不使用B <int> :: mInit,它永远不会被创build。
一个解决scheme将明确定义所需的成员(不初始化它):把某处的源文件a
template<> typename B<int>::InitHelper B<int>::mInit;
这和明确定义模板类的方法基本相同。
-
[1]未注释的情况:没关系。
static InitHelper B<int>::mInit
不存在。 如果不使用模板类(struct)的成员,则不编译。 -
[1]和[2]未注释的情况:没关系。
B<int>::getHelper()
使用static InitHelper B<int>::mInit
和mInit
存在。 -
[1]评论说,[2]没有注释:它在VS2008中适用于我。