C++ 延迟动态初始化问题

C++ 延迟动态初始化问题,c++,language-lawyer,C++,Language Lawyer,考虑本节中的示例,即: /-文件1- #包括“a.h” #包括“b.h” B B; A::A(){ b、 使用() } //-文件2- #包括“a.h” A A; //-文件3- #包括“a.h” #包括“b.h” 外部A; 外部B; int main(){ a、 使用() b、 使用(); } 以下是示例中的注释: 但是,如果在main的第一个语句之后的某个点初始化a,则b将在a中使用之前初始化​::​A 我不明白为什么b在A中使用之前保证初始化​::​当在main的第一个语句之后的某个点初

考虑本节中的示例,即:

/-文件1-
#包括“a.h”
#包括“b.h”
B B;
A::A(){
b、 使用()
}
//-文件2-
#包括“a.h”
A A;
//-文件3-
#包括“a.h”
#包括“b.h”
外部A;
外部B;
int main(){
a、 使用()
b、 使用();
}
以下是示例中的注释:

但是,如果在main的第一个语句之后的某个点初始化a,则b将在a中使用之前初始化​::​A

我不明白为什么
b
在A中使用之前保证初始化​::​当在main的第一个语句之后的某个点初始化时。根据规定:

具有静态存储持续时间的非本地非内联变量的动态初始化是在main的第一条语句之前排序,还是延迟,这是实现定义的如果延迟,则在任何非初始化odr使用任何非内联函数或与待初始化变量定义在同一转换单元中的非内联变量之前发生。

非初始化odr使用是指odr使用([basic.def.odr])不是由非本地静态或线程存储持续时间变量的初始化直接或间接导致的

我能理解的是,当初始化延迟时,变量
a
应该在变量
a
的odr使用(非初始化odr使用)之前进行初始化,该变量位于标有
#2
的位置。然而,我不能理解的是,评论说b将在A中使用之前进行初始化​::​A。IIUC,函数
A::A
的调用是变量
A
初始化的一部分,因此在
#1
处使用变量
b
的odr不是非初始化odr,因为它是由非本地静态或线程存储持续时间变量的初始化直接或间接引起的。我认为它只能说变量
b
保证在
#2
之前被初始化,为什么评论说b将在A中使用之前被初始化​::​A?如何解释这个例子?

从句的演变 所讨论的(非规范性)示例可以追溯到标准的C++98版本,但托管条款中的(规范性)语言在C++17中发生了更改

C++98:

3.6.2非本地对象的初始化[basic.start.init] 3-实现定义了命名空间范围的对象的动态初始化([交叉引用])是否在main的第一条语句之前完成。如果初始化延迟到main的第一个语句之后的某个时间点,则应在首次使用与要初始化的对象在同一翻译单元中定义的任何函数或对象之前进行。[关于副作用的脚注][示例如下]

C++03具有相同的文本。C++11删除了交叉引用,并将“名称空间范围的对象”替换为“具有静态存储持续时间的非局部变量”,“对象”替换为“变量”,“使用”替换为“odr使用”,但我认为该子句的含义不变。C++14没有改变

2017年3月,该语言被修改、发布并转录到标准草案中,及时将其转换为C++17。P0250R3增加了非初始化odr使用的定义,并修改了该条款以引用该定义,同时还以线程感知术语(在之前排序,强烈发生在之前等)表达了事件之间的关系,并添加了关于避免死锁的注释

自那时以来,关于避免僵局的说明已修改为建议的做法

措辞变化的动机 幸运的是,P0250R3包含了动机的讨论。在“顺序程序的并行初始化”一节中,我们读到:

目前,我们非常明确地允许静态构造函数在main启动之后运行,不管是否启动了其他线程。这似乎是出于支持的目的,例如,在引用函数符号时延迟加载动态库,如Posix系统上的RTLD_LAZY。即使在库加载中立即运行静态命名空间作用域构造函数,库也可能在main启动后隐式加载

而且:

SG1通常认为应该避免使用静态命名空间作用域构造函数[…]我们决定将此类构造函数限制在现有线程中,这似乎与已知实现一致

示例的正确性。 我认为这个例子是错误的,而且一直都是错误的

在C++98中,该示例是不正确的,因为该版本标准中的规范性措辞导致循环。假设我们扩充示例,在与
a
定义相同的TU中定义构造函数
B::B

// - File 2 -
#include "a.h"
A a;
B::B() {
   a.Use();
}
现在,根据C++98,
a
的(动态)初始化发生在第一次调用
B::B
之前,并且
B
的初始化发生在第一次调用
a::a
之前。但是
a
的初始化需要调用
a::a
,而
b
的初始化需要调用
b::b
。所以我们有一个循环回归

P0250R3中的措辞更改(将odr使用更改为非初始化odr使用)打破了这种循环,代价是使示例变得毫无意义。但是它总是坏的。这是一个错误,可以通过“第一次使用时构造”习惯用法或通过使用帮助对象(例如)来避免

实施实践 我将示例(循环)编译成一个(Linux,ELF;CentOS 7.8)共享对象,在使用