我也想写一些关于初始化的文字,稍后我可以链接到。
首先是可能性列表。
命名空间静态
- 有两种初始化方法。静态(打算在编译时发生)和动态(打算在运行时发生)初始化。
- 静态初始化发生在任何动态初始化之前,不考虑翻译单元关系。
- 动态初始化在翻译单元中是有序的,而静态初始化没有特定的顺序。同一翻译单元的命名空间范围的对象按照其定义出现的顺序动态初始化。
- 使用常量表达式初始化的 POD 类型对象是静态初始化的。任何对象的动态初始化都可以依赖它们的值,而不管翻译单元关系。
- 如果初始化抛出异常,
std::terminate
则调用。
例子:
以下程序打印A(1) A(2)
struct A {
A(int n) { std::printf(" A(%d) ", n); }
};
A a(1);
A b(2);
以下,基于同一类,打印A(2) A(1)
extern A a;
A b(2);
A a(1);
假设有一个翻译单元,msg
其定义如下
char const *msg = "abc";
然后打印以下内容abc
。注意p
接收动态初始化。但是因为在这之前发生了静态初始化(char const*
是POD类型,"abc"
是地址常量表达式)msg
,这很好,并且msg
保证被正确初始化。
extern const char *msg;
struct P { P() { std::printf("%s", msg); } };
P p;
- 对象的动态初始化不需要不惜一切代价在 main 之前发生。但是,初始化必须在第一次使用其翻译单元的对象或函数之前发生。这对于动态可加载库很重要。
类静态
- 表现得像命名空间静态。
- 有一个关于编译器是否允许在第一次使用其翻译单元的函数或对象时初始化类静态的错误报告(在 main 之后)。标准中的措辞目前仅允许命名空间范围对象使用此功能 - 但似乎它也打算允许类范围对象使用此功能。读取命名空间范围的对象。
- 对于作为模板成员的类静态变量,规则是它们仅在它们被使用时才被初始化。不使用它们不会产生初始化。请注意,在任何情况下,初始化都会像上面解释的那样发生。初始化不会因为它是模板的成员而延迟。
局部静态
- 对于局部静力学,会发生特殊规则。
- 用常量表达式初始化的 POD 类型对象在进入定义它们的块之前被初始化。
- 其他本地静态对象在控制第一次通过它们的定义时被初始化。抛出异常时不认为初始化完成。下次将再次尝试初始化。
示例:以下程序打印0 1
:
struct C {
C(int n) {
if(n == 0)
throw n;
this->n = n;
}
int n;
};
int f(int n) {
static C c(n);
return c.n;
}
int main() {
try {
f(0);
} catch(int n) {
std::cout << n << " ";
}
f(1); // initializes successfully
std::cout << f(2);
}
在上述所有情况下,在某些有限的情况下,对于一些不需要静态初始化的对象,编译器可以对其进行静态初始化,而不是动态初始化。这是一个棘手的问题,请参阅此答案以获取更详细的示例。
还要注意,销毁的顺序是对象构建完成的确切顺序。这是一种常见的情况,在 C++ 中的各种情况下都会发生,包括破坏临时对象。