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?
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.
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).