模板的typedef是否保留静态初始化顺序?

3
在同一编译单元内,C++标准规定静态初始化顺序是明确定义的——它是静态对象声明的顺序。但使用Sun Studio 12编译器时,我遇到了不直观的行为。我定义了一个模板类helper<T>,其中包含一个类型为T的静态成员_data和一个使用_data的静态成员函数foo。在我的.cpp文件中,我把它放在main()之前。
struct A { /* some definition */ };

typedef helper<int> s0;
typedef helper<A> s1;

请注意,helper<int> 的 typedef 在 helper<A> 的前面。因此根据标准,我期望 helper<int>::_data 将会在 helper<A>::_data 之前构造(请记住,_data 是一个静态成员)。在 GCC 上,情况是这样的,但在 Sun 上不是这样。
这是有问题的,因为 A 的构造函数使用了 helper<int>::_data。我只有一个编译单元,并且没有更早的 helper<A> 实例化,所以我认为顺序应该是明确定义的。这是 Sun 编译器的 bug,还是 typedef 在技术上不构成定义/实例化?我的意思是,Sun 编译器的行为是否符合标准?
以下是我的 main():
int main()
{
    //Swapping the order of these has no effect on Sun
    s0::foo();
    s1::foo();
}

s0或s1没有其他用途。

2个回答

6
在同一编译单元中,C++标准规定静态初始化顺序是明确定义的——它是静态对象声明的顺序。在您展示的代码中,没有静态数据成员的声明,只有一个typedef名称的声明。这与静态初始化顺序无关,也不会影响任何顺序。您可能会这样想:如果我进行typedef声明,它将实例化helper,从而首先实例化其静态数据成员声明。但问题是,该行不会导致helper的实例化。要发生这种情况,您需要显式实例化或设法使其隐式实例化(例如创建helper对象,或将其用作嵌套名称限定符,如helper::...,并明确引用静态成员——否则,将省略创建它)。但有一个更深层次的问题。顺序不是静态数据成员的声明顺序,而是它们的定义顺序。请考虑以下内容。
struct C { C() { printf("hey\n"); } };
struct A { 
  static C a;
  static C b;
};

C A::b;
C A::a;

在这段代码中,b在a之前被创建,尽管a在b之前被声明
下面的代码会打印出 2 1
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);

// explicit instantiation of declaration and definition
template struct A<2>;
template struct A<1>;

int main() {

}

但是以下代码不会输出任何内容,除非您在main中取消注释该行。
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);

// implicit instantiation of declarations
A<2> a2;
A<1> a1;

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

我其实不确定第二个代码片段的正确输出是什么。阅读标准后,我无法确定顺序。在14.6.4.1中,它说:“即时实例化点”:
对于函数模板特化、成员函数模板特化或类模板的成员函数或静态数据成员的特化,如果该特化由于被另一个模板特化引用而被隐式实例化[...]。否则,这种特化的实例化点紧随引用特化的命名空间作用域声明或定义之后。
它们定义的实例化点都出现在main的定义之后。哪个定义在另一个定义之前实例化似乎没有明确规定。如果有人知道答案并了解其他编译器的行为(GCC打印1 2,但在main表达式的顺序交换时,打印2 1),请在评论中让我知道。
有关详细信息,请参见关于静态对象生命周期的此答案

对于类模板,实例化点是在 main 函数之前(因为类类型必须完整,不能在之后)。因此,在 main 函数中执行 s0::foo(); 将隐式地立即实例化 s0。在这种情况下,从 _data 引用的上下文取决于模板参数(毕竟它在模板的成员函数内部),因此 _data 的实例化点与 s0 的实例化点相同。但这也存在同样的问题:我不知道 s0s1 的实例化顺序是什么。大多数情况下,这并没有什么区别... - Johannes Schaub - litb
因为ODR(一个定义规则)指出,在程序中,每个模板特化的实例化不能有不同的行为。但是在这里,我们只有一个s0s1的特化,它们根据它们的顺序具有不同的结果。 :( - Johannes Schaub - litb
我想我会在Usenet上问一下,看看那里的人对此有何看法。也许我对我上面写的一切都是错的 =)。顺便说一句,在我的示例中,我已经删除了其中实例化_data的规则,因为我认为这对我的示例不重要。但我现在意识到它对你的示例很重要 :)。 - Johannes Schaub - litb
否则,这种特化的实例化点紧随引用该特化的命名空间作用域声明或定义之后。但是,经过更深入的思考,我并不明白这句话的意思。它似乎表明如果一个函数使用了一个特化的实例化,那么这个特化的实例化会在函数之后被实例化!但是,为了使函数工作,这个实例化必须已经存在。让人困惑 @_@ - Joseph Garvin
@Joseph,这是usenet线程的链接:http://tinyurl.com/nsk2eq(使用Tinyurl,因为我发现SO有时无法正确处理这些链接) - Johannes Schaub - litb
显示剩余5条评论

0

在那段代码中,实际上你没有声明任何对象。

你需要额外的代码:

s0 one;
s1 two;

在这种情况下,这两个对象现在已经被声明,并且应该可以正常工作。
你是否明确声明了一个s0?
尝试使用s0虚拟变量跟随typedef,并查看问题是否得到解决。

根据我的理解,这段代码确实声明了对象。helper<int>拥有一个静态数据成员。因此,每次实例化该模板时,都会创建一个静态数据成员(即一个对象)。typedef语句用于实例化模板... 对吗? - Joseph Garvin
typedef 不会实例化模板。 - Dave Gamble
typedef 的行为本质上类似于 #define。 - Dave Gamble

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接