Why doesn't calling member function invoke the ODR-USE of that object?

℡╲_俬逩灬. 提交于 2019-12-13 12:12:39

问题


Here in cppref says,

If the initialization of a non-inline variable (since C++17) is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized.

And later it gives an example of deferred dynamic initialization:

// - 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();
}

And the comment says:

If a is initialized at some point after the first statement of main (which odr-uses a function defined in File 1, forcing its dynamic initialization to run), then b will be initialized prior to its use in A::A

Why can the if situation happen? Doesn't a.Use() odr-use a thus a must be initialized before this statement?


回答1:


I think you're being misled by the order of things in C++.

Translation Units (TU= one .cpp files and its headers) have no order in C++. They can be compiled in any order, but also in parallel. The only special TU is the one that contains main(), but even that one can be compiled at any time and order.

Within each Translation Unit, there is an order in which initializers appear. This is also the temporal order in which they are initialized, but it might differ from their order in memory (if that is even determined - C++ strictly speaking does not enforce that). This does not cause an order of initializers across Translation Units. It does happen before functions of that Translation Unit are executed, because those functions may rely on the initialized objects.

Functions in a translation unit can of course appear in any order; how they are executed depends on what you've written in them.

Now there are a few things that impose additional ordering constraints. I understand that you are aware of the fact that some initializers can run even after main() has started. If this happens, the ordinary rule still applies that the initializers of a single TU must execute befor functions in that TU.

In this case, TU file1 holds the (default) initializer for b, which must run before A::A in the same TU. As you correctly note, a.Use must happen after the initialization of a. This requires A::A.

Hence, we have the following order relations (where < means precedes)

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

and therefore transitively

b < a.Use

As you can see, it's safe to use a.c in a.Use because the order A::A < a.Use also holds.

You can get into problems, if you make A::A depend on b and B::B depend on a. If you introduce a cyclic dependency, no matter which object is initialized first, it always depend on an object that hasn't been initialized. Don't do that.




回答2:


In short, why bother the order of initialization of a and b?

The example shows nothing that indicates a should be necessarily initialized before b to make the program well-defined.

  • It is true that extern A a; is before extern B b;, but this is nothing to do with the order.

  • It is also true that evaluation in a.Use(); is sequenced before evaluation in b.Use(); in the main function in the TU translated from File 3, but this is still nothing to do with the order.

Making a.Use() to be well-defined has nothing to do with this particular order, unless there are other dependencies (e.g. subobject initialization implies order).

OTOH, if you want the additional order, how do you specify it?

Annex:

The wording "happen after the first statement of main/thread function" is strange. It seems that the intentional one is "does not happen before the first statement of main/thread function", and the editor occasionally missed there can be more than one evaluations applicable to the binary relationship in evaluation of a single statement. This originates from the standard, but it has been corrected by P0250R3.

Actually, I find the example comes from the standard, quoted from N4727 [basic.start.dynamic]/4:

3 A non-initialization odr-use is an odr-use (6.2) not caused directly or indirectly by the initialization of a non-local static or thread storage duration variable.

4 It is implementation-defined whether the dynamic initialization of a non-local non-inline variable with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized.55 It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs. [ Note: Such points should be chosen in a way that allows the programmer to avoid deadlocks. —end note ] [ Example:

// - 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();
}

It is implementation-defined whether either a or b is initialized before main is entered or whether the initializations are delayed until a is first odr-used in main. In particular, if a is initialized before main is entered, it is not guaranteed that b will be initialized before it is odr-used by the initialization of a, that is, before A::A is called. If, however, a is initialized at some point after the first statement of main, b will be initialized prior to its use in A::A. —end example ]

55) A non-local variable with static storage duration having initialization with side effects is initialized in this case, even if it is not itself odr-used (6.2, 6.6.4.1).



来源:https://stackoverflow.com/questions/49814895/why-doesnt-calling-member-function-invoke-the-odr-use-of-that-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!