是否可以保证在main
调用静态类成员之前对其进行初始化?
2 回答
我认为没有:
[C++03: 3.6.2/3]:
命名空间范围对象main
的动态初始化(8.5、9.4、12.1、12.6.1)是否在. 如果初始化延迟到 的第一个语句之后的某个时间点main
,则它应在第一次使用与要初始化的对象相同的翻译单元中定义的任何函数或对象之前发生。
嗯,真的吗?
好吧,可以说,“在命名空间范围内定义”与“命名空间范围的对象”并不完全相同:
[C++03: 9.4.2/2]:
数据成员在其类定义中的声明static
不是定义,并且可能是 cv-qualified 以外的不完整类型void
。数据成员的定义static
应出现在包含该成员的类定义的命名空间范围内。在命名空间范围的定义中,static
数据成员的名称应使用运算符由其类名限定::
。 静态数据成员定义中的初始化表达式在其类 (3.3.6) 的范围内。
但是,它是类范围内的初始化程序;没有提到成员本身除了命名空间范围之外还有其他任何东西(除非我们在脑海中到处static
注入“词法”这个词)。
有这样一段令人愉悦的段落:
[C++03: 9.4.2/7]:
静态数据成员的初始化和销毁与非本地对象(3.6.2、3.6.3)完全相同。
然而,不幸的是main
,关于“非本地对象”的排序和静态初始化的唯一进一步定义是前面提到的[C++03: 3.6.2/3]
.
那怎么办?
我相信 C++11 中的新措辞清楚地表明了这条原本可能模棱两可的规则的意图,它解决了所有问题:
[C++11: 9.4.2/6]:
静态数据成员的初始化和销毁与非局部变量(3.6.2、3.6.3)完全相同。
[C++11: 3.6.2/4]:
具有静态存储持续时间的非局部变量的动态初始化是否在 main 的第一条语句之前完成是实现定义的。[..]
C++03:简而言之,不保证
C++11:不保证,请参阅 Lightness 的回答。
我对 C++03 语句的解释/分析:
术语:[basic.start.init]/1
零初始化和用常量表达式初始化统称为静态初始化;所有其他初始化都是动态初始化。
非本地对象的初始化顺序:
具有静态存储持续时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5)。
但是它没有提到“任何其他初始化”何时发生,即不能保证它会在 main 的第一个语句之前,即使是对于 zero-initialization。
使用常量表达式 (5.19) 初始化的具有静态存储持续时间的 POD 类型 (3.9) 的对象应在任何动态初始化发生之前进行初始化。
但同样,不能保证。
动态初始化
[basic.start.init]/3
命名空间范围的对象的动态初始化(8.5、9.4、12.1、12.6.1)是否在main的第一条语句之前完成是实现定义的。如果初始化延迟到 main 的第一个语句之后的某个时间点,它应该发生在第一次使用与要初始化的对象相同的翻译单元中定义的任何函数或对象之前。
但是什么是“命名空间范围的对象”?我在标准中没有找到任何明确的定义。范围实际上是名称的属性,而不是对象的属性。因此,我们可以将其读作“在命名空间范围内定义的对象”或“由命名空间范围的名称引入的对象”。注意动态初始化后的参考“9.4”。它指的是“静态成员”,只能表示静态数据成员。所以我会说它的意思是“在命名空间范围内定义的对象”,因为静态数据成员是在命名空间范围内定义的:
[class.static.data]/2
静态数据成员的定义应出现在包含该成员的类定义的命名空间范围内。
即使你不同意这种解释,仍然有 [basic.start.init]/1
在同一翻译单元的命名空间范围内定义的静态存储持续时间并动态初始化的对象应按照其定义在翻译单元中出现的顺序进行初始化。
这显然适用于静态数据成员,这意味着如果在静态数据成员的定义之前存在这样的对象,则它们的初始化方式不能与命名空间范围名称中引入的对象不同。也就是说,如果根本无法保证静态数据成员的动态初始化,则由命名空间范围的名称引入的任何先前对象的保证都将适用——它们是:无(它不必在第一次初始化之前被初始化)主要声明)。
如果在静态数据成员的定义之前没有这样的对象并且您不同意解释 - 根本无法保证静态数据成员的动态初始化。
结论
所以我们只能保证动态初始化发生在某个时间(在任何使用之前),加上一个例外,即不能消除带有副作用的初始化。尽管如此,我们仍不能保证任何类型的非本地对象的初始化都会在main
.
注意:有一些解决方法,例如:
#include <iostream>
struct my_class
{
static int& my_var()
{
static int i = 42;
return i;
}
};
int j = ++my_class::my_var();
int k = ++my_class::my_var();
int main()
{
std::cout << j << " : " << k << std::endl;
}