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] 未注释:
[3] 中静态初始化阶段的段错误
因此我的问题是:这是编译器错误还是监视器和椅子之间的错误? 如果是后者:是否有一个优雅的解决方案(即无需显式调用静态初始化方法)?
更新一:
这似乎是一种理想的行为(如 ISO/IEC C++ 2003 标准 14.7.1 中所定义):
除非类模板或成员模板的成员已显式实例化或显式特化,否则当在需要成员定义存在的上下文中引用特化时,会隐式实例化该成员的特化;特别是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身的使用方式需要静态数据成员的定义存在。
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)?
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
前段时间在 usenet 上讨论过这个问题,当时我试图回答 stackoverflow 上的另一个问题: 静态数据成员的实例化点。我认为减少测试用例并单独考虑每个场景是值得的,所以让我们首先看一下更一般的情况:
您有静态数据成员模板的定义。由于
14.7.1
,这尚未创建任何数据成员:根据定义该词的一个定义规则(在
3.2/2
),当“使用”该实体时,需要对该实体(=实体)进行定义。特别是,如果所有引用都来自未实例化的模板、模板的成员或sizeof
表达式或不“使用”实体的类似事物(因为它们要么没有潜在地评估它,要么它们只是还不存在作为本身使用的函数/成员函数),这样的静态数据成员不会被实例化。14.7.1/7
的隐式实例化会实例化静态数据成员的声明 - 也就是说,它将实例化处理该声明所需的任何模板。但是,它不会实例化定义 - 也就是说,不会实例化初始值设定项,并且不会隐式定义该静态数据成员类型的构造函数(标记为已使用)。这一切都意味着,上面的代码将不会输出任何内容。现在让我们隐式实例化静态数据成员。
这会导致两个静态数据成员存在,但问题是——初始化的顺序是怎样的?简单阅读一下,人们可能会认为
3.6.2/1
适用,其中表示(我强调):现在,正如 usenet 帖子中所述,并在此缺陷报告中解释了 ,这些静态数据成员不是在翻译单元中定义的,而是在实例化单元中实例化的,如
2.1/1
中所述:这样的成员的实例化点也并不重要,因为这样的实例化点是实例化与其翻译单元之间的上下文链接 - 它定义了可见的声明(如
14.6.4.1< 中指定的) /code>,并且每个实例化点都必须赋予实例化相同的含义,如
3.2/5
的一个定义规则(最后一个项目符号)中指定的那样。如果我们想要有序初始化,我们必须进行安排,这样我们就不会弄乱实例化,而是使用显式声明——这是显式专业化的领域,因为它们与普通声明并没有真正的不同。事实上,C++0x 将
3.6.2
的措辞更改为以下内容:这对您的代码意味着:
[1]
和[2]
注释:没有引用静态数据成员存在,因此它们的定义(也不是它们的声明,因为不需要实例化B
)不会被实例化。没有副作用发生。[1]
未注释:使用B::getB()
,它本身使用B: :mB
,这要求静态成员存在。该字符串在 main 之前初始化(无论如何在该语句之前,作为初始化非本地对象的一部分)。没有任何内容使用B::mInit
,因此它不会被实例化,因此不会创建B::InitHelper
的对象,这使得它的构造函数不被创建正在使用,这反过来永远不会为B::mB
分配某些内容:您只会输出一个空字符串。[1]
和[2]
未注释:这对你有用就是运气(或者相反:) )。如上所述,对于初始化调用的特定顺序没有要求。它可能在 VC++ 上工作,在 GCC 上失败,在 clang 上工作。我们不知道。[1]
已评论,[2]
未评论:同样的问题 - 再次出现,两者 静态数据成员被使用:B::mInit
被B::getHelper
使用,并且实例化B::mInit
将导致其构造函数被实例化,它将使用B::mB
- 但对于您的编译器来说,顺序是不同的此特定运行(未指定的行为不需要在不同运行之间保持一致):它首先初始化B::mInit
,它将对尚未构造的字符串对象进行操作。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:
You have the definition of a static data member template. This does not yet create any data members, because of
14.7.1
: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 asizeof
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.
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):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
: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 at3.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: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 ofB<int>
) are not instantiated. No side effect occurs.[1]
uncommented:B<int>::getB()
is used, which in itself usesB<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 usesB<int>::mInit
, so it's not instantiated, and so no object ofB<int>::InitHelper
is ever created, which makes its constructor not being used, which in turn will never assign something toB<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 byB<int>::getHelper
, and the instantiation ofB<int>::mInit
will cause its constructor to be instantiated, which will useB<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 initializesB<int>::mInit
first, which will operate on a not-yet-constructed string object.问题是您为静态成员变量给出的定义也是模板。
在编译期间,这实际上没有定义任何内容,因为 T 是未知的。它类似于类声明或模板定义,编译器在看到它时不会生成代码或保留存储。
当您使用模板类时,定义会在稍后隐式发生。因为在出现段错误的情况下,您不使用 B::mInit,因此永远不会创建它。
解决方案是显式定义所需的成员(而不对其进行初始化): 将源文件 a 放在某处
这与显式定义模板类的工作方式基本相同。
The problem is that the defintions you give for the static member variables are templates too.
During compilation, this defines actually nothing, since T is not known. It is something like a class declaration or a template definition, the compiler does not generate code or reserve storage when it sees it.
The definition happens implicitly later, when you use the template class. Because in the segfaulting case you don't use B<int>::mInit, it is never created.
A solution would be explictly defining the needed member (without initializing it): Put somewhere source file a
This works basically the same way as explictly defining a template class.
[1]未注释的情况:
没关系。
static InitHelper B::mInit
不存在。如果不使用模板类(结构)的成员,则不会编译。[1]和[2]未注释的情况:
没关系。
B::getHelper()
使用static InitHelper B::mInit
且mInit
存在。[1]已评论,[2]未评论:
它在 VS2008 中适用于我。
[1] uncommented case:
It is ok.
static InitHelper B<int>::mInit
does not exist. If member of the template class (struct) is not used it does not compiled.[1] and [2] uncommented case:
It is ok.
B<int>::getHelper()
usestatic InitHelper B<int>::mInit
andmInit
exists.[1] commented, [2] uncommented:
it works for me in VS2008.