问题
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 ofmain
(which odr-uses a function defined inFile 1
, forcing its dynamic initialization to run), thenb
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 beforeextern B b;
, but this is nothing to do with the order.It is also true that evaluation in
a.Use();
is sequenced before evaluation inb.Use();
in themain
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
orb
is initialized beforemain
is entered or whether the initializations are delayed untila
is first odr-used inmain
. In particular, ifa
is initialized beforemain
is entered, it is not guaranteed thatb
will be initialized before it is odr-used by the initialization ofa
, that is, beforeA::A
is called. If, however,a
is initialized at some point after the first statement ofmain
,b
will be initialized prior to its use inA::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