Wednesday, September 30, 2020

Digest for comp.lang.c++@googlegroups.com - 14 updates in 1 topic

olcott <NoOne@NoWhere.com>: Sep 29 07:12PM -0500

On 9/29/2020 4:53 PM, David Brown wrote:
> if the statements in the body are simple assignments like the ones
> above, or even some function calls, then the compiler can re-arrange
> them because there is no (defined) way for the program to see a difference.
 
Okay great. How it an initialization different than an assignment under
the covers in the machine code?
 
--
Copyright 2020 Pete Olcott
David Brown <david.brown@hesbynett.no>: Sep 30 08:26AM +0200

On 30/09/2020 02:12, olcott wrote:
>> difference.
 
> Okay great. How it an initialization different than an assignment under
> the covers in the machine code?
 
I hope I don't need to explain to you the difference in C++ between a
constructor and an assignment operator? It can certainly be the case
for simple types that the generated assembly code is the same for both
operations - I'd expect that in this case.
Ralf Goertz <me@myprovider.invalid>: Sep 30 10:29AM +0200

Am Tue, 29 Sep 2020 23:53:44 +0200
> above, or even some function calls, then the compiler can re-arrange
> them because there is no (defined) way for the program to see a
> difference.
 
Why is it then that in
 
struct X {
int i;
float z;
X(float z_, int i_) : z(z_), i(i_) {}
};
 
int main() {
X x(3.2,4);
}
 
with g++ -Wall I get:
ini.cc: In constructor 'X::X(float, int)':
ini.cc:3:11: warning: 'X::z' will be initialized after [-Wreorder]
3 | float z;
| ^
ini.cc:2:9: warning: 'int X::i' [-Wreorder]
2 | int i;
| ^
ini.cc:4:5: warning: when initialized here [-Wreorder]
4 | X(float z_, int i_) : z(z_), i(i_) {}
| ^
 
What is the point of that warning? If the compiler can (in this case
actually) initialize in any order why can't (or shouldn't) I write it in
any order? Even if the initialization of one member "foo" requires the
other member "bar" to be initialized first the compiler can figure out
the necessary order. And foo itself can't know anything about the the
position of bar in the struct so that it would matter, right?
David Brown <david.brown@hesbynett.no>: Sep 30 12:04PM +0200

On 30/09/2020 10:29, Ralf Goertz wrote:
> other member "bar" to be initialized first the compiler can figure out
> the necessary order. And foo itself can't know anything about the the
> position of bar in the struct so that it would matter, right?
 
You have to understand the difference between the semantics of the
language, and the code generated by the compiler.
 
The language rules say that for member initialisation like this, the
order follows the order of declaration in the class, not the order in
the initialisation clause. gcc helpfully warns you if the
initialisation clause does not match, because there you have code that
looks different from its actual meaning.
 
Within a constructor body, the order of assignment (not initialisation)
follows the order of the statements.
 
 
When generating object code for all this, a compiler can re-arrange
things in all sorts of ways to get more efficient object code, as long
as the result is identical in terms of the observable behaviour for the
program. Since C++ code cannot access either "i" or "z" within an X
object before the creation is finished, it doesn't matter which one is
/actually/ initialised first in the object code. In your example, since
"x" is not used, the compiler won't bother creating the object at all.
If you instead have:
 
X foo() {
X x(3.2,4);
return x;
}
 
the compiler (in my tests) will generate a single 64-bit load
instruction - it initialises both fields at the same time.
 
But if the fields had been more complex types, with their own
non-trivial constructors, the order /could/ matter and the compiler
would generate code that ordered initialisation according to the rules
of the language.
Paavo Helde <myfirstname@osa.pri.ee>: Sep 30 04:04PM +0300

30.09.2020 11:29 Ralf Goertz kirjutas:
> 4 | X(float z_, int i_) : z(z_), i(i_) {}
> | ^
 
> What is the point of that warning?
 
I have seen this warning many times after code refactoring and it's one
of the most annoying ones. The compiler is trying to be too smart, or
not smart enough. It sees that you have written the mem-initializers in
the wrong order, and assumes you are stupid and you think this is the
order in which they are initialized. Thus it "helpfully" informs you
that this is not so.
 
If the compiler were a bit smarter, it would see these initializations
do not depend on each other and therefore it does not matter at all in
which order they get initialized, so there is no need for a warning.
 
Alternatively, if the compiler trusted the user to know the language
rules, there would be no need for a warning if the user writes the
initializers in a random order, as the user obviously trusts the
compiler for putting them into correct order automatically.
 
 
 
> any order? Even if the initialization of one member "foo" requires the
> other member "bar" to be initialized first the compiler can figure out
> the necessary order.
 
The compiler is not allowed to figure out the "correct" order, it has to
follow the declaration order.
 
I guess it's not clear for everybody why language rules are written the
way they are. The reason is simple, the members need to be destructed in
the opposite order of creation, regardless of by which constructor or in
which TU the object was constructed, and the declaration order is the
only order the destructor is guaranteed to know about.
 
And yes, sometimes the member initialization order is important. For
example, if a class contains a std::thread as a member, working with the
same object, then the thread member must be the last one declared,
otherwise the thread may start running before the main object is fully
constructed. Good luck for having the compiler to try to figure out such
things automatically!
Ralf Goertz <me@myprovider.invalid>: Sep 30 03:15PM +0200

Am Wed, 30 Sep 2020 12:04:19 +0200
> the initialisation clause. gcc helpfully warns you if the
> initialisation clause does not match, because there you have code that
> looks different from its actual meaning.
 
Okay, but in the declaration you declare and nothing else. Why does it
matter in the initialzation phase that you had a different order when
you declared the class. Of course, if the initialization of (in my
example) z depended on i like in
 
X(float z_, int i_) : z(z_+i), i(i_) {}
 
then the order of initialization matters. So in this case gcc could warn
about the usage of uninitialized members or so. But I still couldn't
care less, whether z was *declared* before i or vice versa.
 
> non-trivial constructors, the order /could/ matter and the compiler
> would generate code that ordered initialisation according to the rules
> of the language.
 
Can you give an example for that that would not be covered by the usage
of uninitialzed members? And even if you can why isn't then the order of
initialization the one I should care about. What I mean is why should I
or the compiler care that the declaration order is different. Is that
important in any way other than that it differs and the language says it
shouldn't?
James Kuyper <jameskuyper@alumni.caltech.edu>: Sep 30 09:31AM -0400

On 9/29/20 4:51 PM, David Brown wrote:
 
> And as always, the compiler can re-arrange that into any order it wants
> if the difference can't be seen by code. (That applies to both the
> member initialisation part, and the constructor body.)
 
Correct - which means that the issue only comes up when the order in
which the initializations occur does matter. If that is the case, you
can guarantee that the observable results are the same "as if" the
initializations had occurred in the required order, by specifying that
order in the constructor, even if that's not the same as the declaration
order (modulo access controls).
Ralf Goertz <me@myprovider.invalid>: Sep 30 03:41PM +0200

Am Wed, 30 Sep 2020 16:04:39 +0300
> otherwise the thread may start running before the main object is fully
> constructed. Good luck for having the compiler to try to figure out
> such things automatically!
 
But that does only mean that I need to initialize in the correct order.
But the declaration order only matters because the language standards
say so, right?
Manfred <noname@add.invalid>: Sep 30 05:23PM +0200

On 9/30/2020 3:41 PM, Ralf Goertz wrote:
 
> But that does only mean that I need to initialize in the correct order.
> But the declaration order only matters because the language standards
> say so, right?
 
The initialization order is important. And the standard tells you that
in order to control the initialization order you have to arrange the
/declaration/ order accordingly.
 
I would also like to stress that the initialization order is important
to be univocally defined for self-consistency of the object itself, i.e.
for the object to work.
Paavo has already mentioned the destructor requirement of reverse
destruction order (which is both a consistency requirement and a
performance enabler).
More generally member objects may interact with each other, so a
well-defined unique order of initialization is required for this to work.
Since there can be multiple constructors, the natural choice is the
order of declaration, which is guaranteed to be univocal across all
translation units.
Paavo Helde <myfirstname@osa.pri.ee>: Sep 30 07:53PM +0300

30.09.2020 16:41 Ralf Goertz kirjutas:
 
> But that does only mean that I need to initialize in the correct order.
> But the declaration order only matters because the language standards
> say so, right?
 
The requirement that members are deleted in reverse order of creation is
a hard one, this is related to RAII and without that guarantee it would
not be possible to build robust solutions if there are dependencies
between members (and if there are no such dependencies, then the order
of construction is not relevant at all, so no need to worry about that).
 
Thus, knowing the order of construction is needed for the destructor at
least. Theoretically it might be possible to record the order
dynamically in the constructor and store it inside the object, but this
would make the object larger and the destructor code more complicated.
Also, this would imply that different constructors may construct the
members in different order, making their dependencies and the life of
the programmer much more complex.
 
In short, it would introduce some scripting-language-like dynamic
indeterminism into the C++ fundamental data structures, for very little
benefit. I believe nobody wants to go that way. If you really want to
create member objects dynamically in arbitrary order, use e.g.
std::map<std::string, std::any>, it's not that it is not possible to do
such things in C++.
olcott <NoOne@NoWhere.com>: Sep 30 12:24PM -0500

On 9/30/2020 1:26 AM, David Brown wrote:
> constructor and an assignment operator? It can certainly be the case
> for simple types that the generated assembly code is the same for both
> operations - I'd expect that in this case.
 
Someone here seemed to indicate that Initialization may require fewer
assembly language steps than construction.
 
--
Copyright 2020 Pete Olcott
David Brown <david.brown@hesbynett.no>: Sep 30 09:58PM +0200

On 30/09/2020 19:24, olcott wrote:
>> operations - I'd expect that in this case.
 
> Someone here seemed to indicate that Initialization may require fewer
> assembly language steps than construction.
 
Initialisation always involves construction, so that statement does not
make sense.
 
For simple types with trivial construction and assignment, such as those
in examples in this thread, initialisation and assignment will likely
give the same generated code.
David Brown <david.brown@hesbynett.no>: Sep 30 10:02PM +0200

On 30/09/2020 15:15, Ralf Goertz wrote:
 
> Okay, but in the declaration you declare and nothing else. Why does it
> matter in the initialzation phase that you had a different order when
> you declared the class.
 
Why does initialisation order follow declaration order? Because that's
how the language is defined. I don't know /why/ it is defined that way,
but presumably it makes sense in some way.
 
Or do you mean, why does gcc (with the right flags) warn you when your
initialisers don't follow declaration order? That's simpler - as I
said, it is warning you because your code /appears/ to mean an order
that is different from the /actual/ order.
 
>> of the language.
 
> Can you give an example for that that would not be covered by the usage
> of uninitialzed members?
 
Make some classes whose constructors print out a message. Then make a
class containing members of those classes. The order of initialisation
of the members of the big class are then important to the output of the
program.
 
James Kuyper <jameskuyper@alumni.caltech.edu>: Sep 30 05:01PM -0400

On 9/30/20 4:02 PM, David Brown wrote:
> On 30/09/2020 15:15, Ralf Goertz wrote:
>> Am Wed, 30 Sep 2020 12:04:19 +0200
...
 
> Why does initialisation order follow declaration order? Because that's
> how the language is defined. I don't know /why/ it is defined that way,
> but presumably it makes sense in some way.
 
Leaving the order unspecified would cause problems in those cases where
the order matters. Having initialization order follow declaration order
is pretty much the simplest rule that could be defined, and puts the
order fully under the control of the class definition. I doubt that
there's any more complicated reasons than that for this decision.
 
...
> class containing members of those classes. The order of initialisation
> of the members of the big class are then important to the output of the
> program.
 
Ralf: keep in mind that Dave's suggestion of printing merely serves as
an example. Any other shared resource would serve equally well as a
reason why the order would matter.
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: