为什么不调用成员函数调用该对象的ODR-USE?

时间:2018-04-13 10:29:24

标签: c++ initialization language-lawyer one-definition-rule

这里cppref说,

  

如果非内联变量(自C ++ 17)的初始化推迟发生在主/线程函数的第一个语句之后,它发生在任何第一个odr-use 之前具有与要初始化的变量相同的转换单元中定义的静态/线程存储持续时间的变量。

后来它给出了延迟动态初始化的一个例子:

// - File 1 -
#include "a.h"
#include "b.h"
B b;
A::A(){ b.Use(); }

// - File 2 -
#include "a.h"
A a;

// - File 3 -
#include "a.h"
#include "b.h"
extern A a;
extern B b;

int main() {
  a.Use();
  b.Use();
}

评论说:

  

如果在a 的第一个语句之后的某个时刻初始化main(odr-使用File 1中定义的函数,强制进行动态初始化要运行),那么b将在A :: A

中使用之前进行初始化

为什么 if 情况会发生?那么a.Use() odr-use a因此a必须在此声明之前初始化吗?

2 个答案:

答案 0 :(得分:0)

我认为你被C ++中的事物顺序误导了。

翻译单元(TU =一个.cpp文件及其标题)在C ++中没有顺序。它们可以按任何顺序编译,也可以并行编译。唯一特殊的TU是包含main()的TU,但即使是那个也可以随时编译。

在每个翻译单元中,都有一个初始化程序出现的顺序。这也是它们被初始化的时间顺序,但它可能与它们在内存中的顺序不同(如果甚至确定 - 严格来说C ++并不强制执行)。这不会导致翻译单元的初始化程序的顺序。 在执行该翻译单元的功能之前发生,因为这些功能可能依赖于初始化的对象。

翻译单元中的功能当然可以按任何顺序出现;它们的执行方式取决于你写的内容。

现在有一些事情会产生额外的排序限制。我知道您知道某些初始化程序甚至可以在main()启动后运行。如果发生这种情况,普通规则仍然适用于单个TU的初始值设定项必须执行该TU中的函数。

在这种情况下,TU file1拥有b的(默认)初始值设定项,该初始值设定项必须在同一个TU中A::A之前运行。正如您所记录的那样,a.Use必须在a初始化后发生。这需要A::A

因此,我们有以下顺序关系(其中<表示precedes

b < A::A
A::A < a
a < a.Use

因此可以传递

b < a.Use

正如您所看到的,在a.c中使用a.Use是安全的,因为订单A::A < a.Use也有效。

如果A::A取决于bB::B取决于a可能会遇到问题。如果引入循环依赖项,则无论首先初始化哪个对象,它始终依赖于尚未初始化的对象。不要那样做。

答案 1 :(得分:0)

简而言之,为什么要打扰ab的初始化顺序?

示例显示没有任何内容表明a 必须在b之前初始化才能使程序定义明确。

  • extern A a;确实在extern B b;之前,但这与订单无关。

  • a.Use();中的评估在文件3翻译的TU中b.Use();函数的main中的评估之前进行了排序,但这仍然无关紧要做订单。

使a.Use()定义明确与此特定顺序无关,除非存在其他依赖关系(例如,子对象初始化意味着顺序)。

OTOH,如果你想要额外的订单,你如何指定它?

附件:

措辞&#34;发生在主/线程功能的第一个声明之后&#34;很奇怪似乎有意的是&#34;在主/线函数&#34;的第一个语句之前没有发生,并且编辑器偶尔会错过,在评估单个语句时可以有多个适用于二进制关系的评估。这源于标准,但已由P0250R3更正。

实际上,我发现这个例子来自N4727引用的标准[basic.start.dynamic] / 4:

  

3 非初始化odr-use 是一种odr-use(6.2),不是由a的初始化直接或间接引起的   非本地静态或线程存储持续时间变量。

     

4实现定义是否使用static动态初始化非本地非内联变量   存储持续时间在main的第一个语句之前排序或延迟。如果推迟,则强烈推断   在任何非初始化odr使用之前发生的任何非内联函数或非内联变量   与要初始化的变量相同的转换单元.55它是实现定义的线程和   在程序中哪些点发生延迟动态初始化。 [注意:这些要点应该是   选择的方式允许程序员避免死锁。 - 尾注] [例子:

// - File 1 -
#include "a.h"
#include "b.h"
B b;
A::A(){
    b.Use();
}

// - File 2 -
#include "a.h"
A a;

// - File 3 -
#include "a.h"
#include "b.h"
extern A a;
extern B b;
int main() {
    a.Use();
    b.Use();
}
  

执行定义是在a输入之前初始化b还是main还是   初始化会延迟到amain首次使用a。特别是,如果在main之前初始化b   输入后,无法保证aA::A初始化之前被初始化,即   在调用a之前。但是,如果在main的第一个语句之后的某个时刻初始化了bA::A将会   在{{1}}中使用之前初始化。 - 例子]

     

55)在这种情况下,初始化具有静态存储持续时间且具有副作用的初始化的非局部变量,即使它是   本身并没有使用(6.2,6.6.4.1)。