Mr.*_*Mr. 38 c++ templates initialization static-members
对于静态成员初始化,我使用嵌套的帮助器结构,它适用于非模板化的类.但是,如果封闭类由模板参数化,则如果未在主代码中访问辅助对象,则不会实例化嵌套初始化类.为了说明,一个简化的例子(在我的例子中,我需要初始化一个向量).
#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]
}
Run Code Online (Sandbox Code Playgroud)
使用g ++ 4.4.1:
[1]和[2]评论说:
A = Hello, I'm A.
按预期工作
[1]未注释:
A = Hello, I'm A. B =
我希望,InitHelper初始化mB
A = Hello, I'm A. B = Hello, I'm B.按预期工作
因此我的问题是:这是一个编译器错误还是坐在显示器和主席之间的错误?如果是后者:是否有一个优雅的解决方案(即没有显式调用静态初始化方法)?
更新I:
这似乎是一种理想的行为(如ISO/IEC C++ 2003标准14.7.1中所定义):
除非已显式实例化或明确专门化类模板或成员模板的成员,否则在需要成员定义存在的上下文中引用特化时,将隐式实例化成员的特化; 特别是,除非静态数据成员本身以需要静态数据成员的定义存在的方式使用,否则不会发生静态数据成员的初始化(以及任何相关的副作用).
Joh*_*itb 37
这在一段时间之前在usenet上进行了讨论,而我正在尝试回答有关stackoverflow的另一个问题:静态数据成员的实例化.我认为值得减少测试用例,并且单独考虑每个场景,所以让我们首先看一下它:
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;
Run Code Online (Sandbox Code Playgroud)
您有静态数据成员模板的定义.这还没有创建任何数据成员,因为14.7.1:
"...特别是,除非静态数据成员本身以需要静态数据成员定义存在的方式使用,否则不会发生静态数据成员的初始化(以及任何相关的副作用)."
根据定义该单词(at 3.2/2)的一个定义规则,当该实体被"使用"时,需要定义某事物(=实体).特别是,如果所有引用都来自未实例化的模板,模板或sizeof表达式的成员或不"使用"该实体的类似事物(因为它们不是可能正在评估它,或者它们仅仅作为函数不存在) /本身使用的成员函数),这样的静态数据成员不会被实例化.
通过14.7.1/7实例化静态数据成员的声明来实现隐式实例化 - 也就是说,它将实例化处理该声明所需的任何模板.但是,它不会实例化定义 - 也就是说,初始化器不会被实例化,并且静态数据成员类型的构造函数不会被隐式定义(标记为已使用).
这一切都意味着,上面的代码还没有输出.现在让我们引起静态数据成员的隐式实例化.
int main() {
A<1>::c; // reference them
A<2>::c;
}
Run Code Online (Sandbox Code Playgroud)
这将导致两个静态数据成员存在,但问题是 - 初始化顺序如何?在一个简单的阅读中,人们可能会认为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]取消注释: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首先初始化,它将在尚未构造的字符串对象上运行.问题是您为静态成员变量给出的定义也是模板。
template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;
Run Code Online (Sandbox Code Playgroud)
在编译期间,这实际上没有定义任何内容,因为 T 是未知的。它类似于类声明或模板定义,编译器在看到它时不会生成代码或保留存储。
当您使用模板类时,定义会在稍后隐式发生。因为在出现段错误的情况下,您不使用 B<int>::mInit,因此永远不会创建它。
解决方案是显式定义所需的成员(而不对其进行初始化):将源文件 a 放在某处
template<>
typename B<int>::InitHelper B<int>::mInit;
Run Code Online (Sandbox Code Playgroud)
这与显式定义模板类的工作方式基本相同。