Is this code clear regardless of copy?

Consider this code:

#include <iostream> struct Test { int x; int y; }; Test func(const Test& in) { Test out; out.x=in.y; out.y=in.x; return out; } int main() { Test test{1,2}; std::cout << "x: " << test.x << ", y: " << test.y << "\n"; test=func(test); std::cout << "x: " << test.x << ", y: " << test.y << "\n"; } 

One would expect this conclusion:

 x: 1, y: 2 x: 2, y: 1 

and this is really what I get. But due to copying, can elision out be in the same memory location as in , and as a result, the last line of output will be x: 2, y: 2 ?

I tried compiling with gcc and clang with -O0 and -O3 and the results still look as intended.

+8
c ++ undefined-behavior copy-elision
source share
5 answers

This is well-formed code, optimization cannot break well-formed code, because it violates the as-if rule. Which tells us that:

In particular, they do not need to copy or emulate the structure of an abstract machine. Rather, appropriate implementations are required to emulate (only) the observed behavior of an abstract machine, as explained below

with the exception for copying:

[...] implementations are allowed to omit the copy / move construction of the object class, even if the constructor selected for the copy / move operation and / or the destructor for the object have side effects. [...]

But the rules of the sequence still have to be respected, and if we move on to the draft of the standard, we see that the assignment is ordered after the left and right operands are evaluated from section 5.17:

In all cases, the assignment is ordered after calculating the value of the right and left operands and before calculating the value of the assignment expression

and we know that the function body is indefinitely sequenced relative to other estimates that are not related to the function call from section 1.9:

Each estimate in the calling function (including other function calls) that is not otherwise sequenced before or after the body of the called function is executed is indefinitely sequenced with respect to the execution of the called function 9

and indefinitely sequenced means:

Scores A and B are indefinitely sequenced when either A is sequenced before B or B is sequenced to A, but it is not determined that. [Note. Uncertainly ordered estimates cannot overlap, but they can be performed first. -end note]

+3
source share

No, he can not. Optimizations cannot break well-formed code, and this code is well-formed.

EDIT: A small update. Of course, my answer suggests that the compiler itself does not contain errors, which, of course, you can only pray for :)

EDIT2: Some people talk about side effects in copy constructors and that they are bad. Of course they are not bad. They, as I see it, are that in C ++ you are not guaranteed to have a known number of temporary objects created. You are guaranteed that every temporary object created will be destroyed. While optimizations are allowed to reduce the number of temporary objects by copying, they can also increase it! :) As long as your side effects are encoded with this in mind, you are good.

+4
source share

No, it could not!

Optimization does not mean that you get undefined behavior in well-written (not poorly prepared) code.

Check this ref:

appropriate implementations are needed to emulate (only) the observed behavior of an abstract machine, as explained below ....

The corresponding implementation executing a well-formed program should provide the same observable behavior as one of the possible sequences of execution of the corresponding instance of an abstract machine with the same program and the same input ....

The observed behavior of an abstract machine is its sequence of reading and writing to volatile data and calls to library I / O functions ....

taken from that.

In the answer, you can see a case where copy-elision may produce other output!

+3
source share

The only thing that elision is allowed to copy is when you have side effects in your copy constructor. This is not a problem because copy constructors should always be free of side effects.

Just to illustrate, here is a copy constructor with side effects. The behavior of this program really depends on the compiler optimizations, that is, whether the copy is really made or not:

 #include <iostream> int global = 0; struct Test { int x; int y; Test() : x(0), y(0) {} Test(Test const& other) : x(other.x), y(other.y) { global = 1; // side effect in a copy constructor, very bad! } }; Test func(const Test& in) { Test out; out.x=in.y; out.y=in.x; return out; } int main() { Test test; std::cout << "x: " << test.x << ", y: " << test.y << "\n"; test=func(test); std::cout << "x: " << test.x << ", y: " << test.y << "\n"; std::cout << global << "\n"; // output depends on optimisation } 

The code you showed is free of such side effects, and the behavior of your program is clearly defined.

+2
source share

Elision is a combination of lifetimes and object identities.

Elization can occur between a temporary (anonymous object) and the named object that it uses (directly), and between a function-local variable that is not a function argument and the return value of the function.

Elision commutes, essentially. (If objects A and B are dumped together, and B and C are dumped together, then essentially A and C are deleted together).

To return the return value of a function with a variable outside the function, you must directly construct this return value from the return value. Although in some contexts a constructed variable can be named before it is created, its use (similar to the one above) is undefined behavior until the constructor appears.

This constructor of the external variable is sequenced after the func body, and therefore after the func call. Thus, this could not have happened before calling func .

Here is an example of a case when we named a variable before it was created and passed it to func , then we initialized the variable with the func return value. It seems that the compiler decided not to step back in this case, but, as noted in the comments below, id really did: my call to UB hid the elite. (the convolution was an attempt to prevent the compiler from precomputing the values โ€‹โ€‹of test.x and test.y).

+1
source share

All Articles