Second attempt. Hope this is more concise and clear.
I am going to ignore RVO almost completely for this discussion. This makes it really confusing as to what should happen without optimization — it's just moving around copy semantics.
To help with this, the link here will be very useful in value types in C ++ 11 .
When to move?
naming
They never move. They refer to variables or storage locations that are potentially referenced elsewhere, and therefore should not contain their contents in another instance.
prvalue
The above define them as "expressions that do not have identity." Clearly, nothing else can refer to an unnamed value so that they can be moved.
Rvalue
The general case of the "right" meaning, and the only thing from which they can be transferred. They may or may not have named links, but if they do, this is the last such use.
xvalue
This is a kind of mixture of both - they have an identity (are a link), and they can be moved. They should not have a named variable. Cause? They are eXpiring values that must be destroyed. Consider their "final link". xvalues can only be generated from rvalues, so / how std::move works when converting lvalues to xvalues (via the result of a function call).
glvalue
Another type of mutant with its cousin rvalue, it can be either the value of x or lvalue - it has an identity, but it is unclear whether this is the last reference to the variable / storage or not, so it is unclear whether or not it can move from.
Resolution Procedure
If there is an overload that can either accept const lvalue ref or rvalue ref and pass rvalue, the value of rvalue is limited, otherwise the version of lvalue is used. (moving for rvalues, copying otherwise).
Where can this happen
(suppose all types A are not mentioned)
This only happens when the object is “initialized from a value x of the same type”. xvalues are bound to rvalues, but not as limited as pure expressions. In other words, movable things are more than unnamed links, they can also be the "last" link to an object with respect to compiler awareness.
initialization
A a = std::move(b);
passing an argument to a function
void f( A a ); f( std::move(b) );
return function
A f() {
Why does this not happen in f
Consider this variation on f:
void action1(A & a) {
You cannot treat A as an lvalue inside f . Since this is an lvalue , it must be a reference, explicit or not. Each simple variable is technically a reference to itself.
Where we travel. Since A is an lvalue for f purposes, we actually return an lvalue.
To explicitly generate an rvalue, we must use std::move (or generate the result of A&& another way).
Why does this happen in g
With this under our belts consider g
A g(A a) { action1( a );
Yes, A is an lvalue for the purposes of action1 and action2 . However, since all references to A exist only inside g (is it a copy or a moved copy), it can be considered xvalue in the return.
But why not in f ?
There is no special magic for && . Indeed, you should think of it as a link in the first place. The fact that we require an rvalue reference in f , unlike an lvalue reference with A& , does not alter the fact that, as a link, it must be an lvalue, because the storage location of A is external to f , and as for any compiler.
The same does not apply in g , where it is clear that the storage A is temporary and exists only when g called and at no other time. In this case, this is explicitly the value of x and can be moved.
rvalue ref vs lvalue ref and reference pass security
Suppose we overload a function to accept both types of links. What will happen?
void v( A & lref ); void v( A && rref );
The only time void v( A&& ) will be used as described above ("Where it can happen"), otherwise void v( A& ) . That is, rvalue ref will always try to bind to the refval ref signature before attempting to overload the lvalue ref. The value of lvalue ref should never be associated with rvalue ref, unless it can be considered as the value of x (guaranteed destruction in the current area, whether we want it or not).
It is tempting to say that in the case of rvalue we know for sure that the object being transferred is temporary. This is not relevant. This is a signature designed to bind links to what seems like a temporary object.
For an analogy, as well as to do int * x = 23; - this may be wrong, but you can (eventually) force it to compile with poor results if you run it. The compiler cannot say for sure if you are serious about this or pull his leg.
In terms of security, you must consider the functions that do this (and why not do it - if it still compiles at all):
A & make_A(void) { A new_a; return new_a; }
While there is nothing supposedly wrong in the aspect of the language - types work, and we get a link to somewhere back, because new_a the storage location is inside the function, the memory will be returned / invalid when the function returns. Therefore, everything that uses the result of this function will deal with freed memory.
Similarly, A f( A && a ) intended, but not limited, to accept prvalues or xvalues if we really want to drive away something else. Which is where std::move comes in, and let's do just that.
The reason for this is that it differs from A f( A & a ) only in which contexts will be preferred over the rvalue overload . In all other respects, it is identical to how A handled by the compiler.
The fact that we know that A&& is a signature reserved for traffic is controversial; it is used to determine the version of the “reference to the A -type parameter” that we want to bind, the sort in which we must take ownership (rvalue) or the sort in which we must not take ownership (lvalue) of the underlying data (t .e. move it to another place and wipe the instance / link that we give). In both cases, what we are working with is a reference to a memory that is not controlled by f .
Whether we will do it or not, this is not what the compiler can say; it falls into the area of "common sense" programming, for example, so as not to use memory locations that do not make sense to use, but otherwise are the correct memory cells.
What the compiler knows about A f( A && a ) is not to create a new repository for A , since we will be provided with an address (link) for work. We can leave the original address untouched, but the whole idea here is that by declaring A&& we tell the compiler “hey! Give me references to objects that are about to disappear so that I can do something before this happens " The key word here may be, and again the fact that we can explicitly configure this function signature incorrectly.
Consider whether we had version A , which when constructing the move did not delete the old instance data, and for some reason we did it by design (say, we had our own memory allocation functions and knew exactly how our memory model would store data during the lifetime of objects).
The compiler cannot know this, because to determine what happens to objects when they are processed in rvalue bindings, he will analyze the code, this is a question of human judgment at this moment. In the best case, the compiler sees "link, yay, without allocating extra memory here" and following the rules for passing links.
It is safe to assume that the compiler thinks: "This is a link, I do not need to deal with its lifetime in memory inside f , this temporary will be deleted after completion of f .
In this case, when the temporary is passed to f , the memory of this temporary file will disappear as soon as we leave f , and then we will potentially be in the same situation as A & make_A(void) - very bad.
The problem of semantics ...
std::move
The very purpose of std::move is to create rvalue links. By and large, what he does (if nothing else), this leads to the fact that the resulting value is tied to the values of r, unlike lvalues. The reason for this is the reverse signature of A& before rvalue references are available, was ambiguous for things like operator overloads (and, of course, others).
Operators Example
class A {
Please note that this will not accept temporary objects for any operator! It also makes no sense to do this in the case of copying objects - why do we need to change the original object that we copy, perhaps to have a copy that we can change later. It is for this reason that we must declare const A & parameters for copy operators and any situation where it is necessary to copy a link, as a guarantee that we do not modify the original object.
Naturally, this is a problem with moves, where we must change the original object in order to exclude premature release of new container data. (hence the operation "move").
To solve this problem, T&& announcements appear that replace the above sample code and, in particular, target objects in situations where the above will not be compiled. But we would not need to change operator+ as a move operation, and it would be difficult for you to find a reason for this (although you might think). Again, due to the assumption that the addition should not change the original object, only the left-operand object in the expression. Therefore, we can do this:
class A {
Here you should pay attention to the fact that, although A() returns a temporary value, it can not only bind to const A& , but should due to the expected semantics of addition (that it does not change its right operand). The second version of the assignment is clear why you should expect a change in only one of the arguments.
It is also clear that a transition will occur upon assignment, and no movement will occur with rhs in operator+ .
Separation of return semantics and argument binding semantics
The reason that there is only one step above is clear from the definitions of a function (well, an operator). The important thing is that we really associate what is explicitly the value of xvalue / rval with what is unmistakably the lvalue in operator+ .
I must emphasize this point: in this example there is no effective difference in how operator+ and operator= relate to their argument. As for the compiler, in any function body, the argument is effectively const A& for + and A& for = . The only difference is const ness. The only way A& and A&& differ is by distinguishing signatures, not types.
Different semantics come out with different signatures; this is a compiler tool for distinguishing certain cases when otherwise there is no clear difference from the code. The behavior of the functions themselves - the body of the code - may not be able to talk about cases separately!
Another example of this is operator++(void) vs operator++(int) . The first expects to return to its base value before the increase operation, and the second - subsequently. There is no skipping int , so the compiler has two signatures to work with - there is no other way to specify two identical functions with the same name, and as you may or may not know, this illegally overloads the function only for the return type for the same ambiguity reasons.
rvalue variables and other odd situations - exhaustive test
In order to unambiguously understand what is happening in f , I put together a buffet of the fact that one “should not try, but look as if they will work”, which greatly enhances the compiler’s hand on this issue:
void bad (int && x, int && y) { x += y; } int & worse (int && z) { return z++, z + 1, 1 + z; } int && justno (int & no) { return worse( no ); } int num () { return 1; } int main () { int && a = num(); ++a = 0; a++ = 0; bad( a, a ); int && b = worse( a ); int && c = justno( b ); ++c = (int) 'y'; c++ = (int) 'y'; return 0; }
g++ -std=gnu++11 -O0 -Wall -c -fmessage-length=0 -o "src\\basictest.o" "..\\src\\basictest.cpp"
..\src\basictest.cpp: In function 'int& worse(int&&)': ..\src\basictest.cpp:5:17: warning: right operand of comma operator has no effect [-Wunused-value] return z++, z + 1, 1 + z; ^ ..\src\basictest.cpp:5:26: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' return z++, z + 1, 1 + z; ^ ..\src\basictest.cpp: In function 'int&& justno(int&)': ..\src\basictest.cpp:8:20: error: cannot bind 'int' lvalue to 'int&&' return worse( no ); ^ ..\src\basictest.cpp:4:7: error: initializing argument 1 of 'int& worse(int&&)' int & worse (int && z) { ^ ..\src\basictest.cpp: In function 'int main()': ..\src\basictest.cpp:16:13: error: cannot bind 'int' lvalue to 'int&&' bad( a, a ); ^ ..\src\basictest.cpp:1:6: error: initializing argument 1 of 'void bad(int&&, int&&)' void bad (int && x, int && y) { ^ ..\src\basictest.cpp:17:23: error: cannot bind 'int' lvalue to 'int&&' int && b = worse( a ); ^ ..\src\basictest.cpp:4:7: error: initializing argument 1 of 'int& worse(int&&)' int & worse (int && z) { ^ ..\src\basictest.cpp:21:7: error: lvalue required as left operand of assignment c++ = (int) 'y'; ^ ..\src\basictest.cpp: In function 'int& worse(int&&)': ..\src\basictest.cpp:6:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ ..\src\basictest.cpp: In function 'int&& justno(int&)': ..\src\basictest.cpp:9:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ 01:31:46 Build Finished (took 72ms)
This is the unchanged header of the sans build assembly that you do not need to see :) I will leave it as an exercise to understand the errors found, but after reading my own explanations (especially later on), it should be obvious that every error was caused and why, imo in anyway.
Conclusion - What can we learn from this?
First, note that the compiler treats function bodies as separate units of code. This is basically the key. Regardless of what the compiler does with the function body, it cannot make assumptions about the behavior of the function, which requires that the function body be changed. There are templates to deal with these cases, but this is beyond the scope of this discussion - just note that the templates generate several function bodies to handle different cases, while otherwise the same function organ must be reused in each case when a function can be used.
Secondly, rvalue types were predominantly provided for relocation operations - a very specific circumstance that should have occurred during the assignment and construction of objects. Another semantics using rvalue reference bindings goes beyond any compiler that you have to deal with. In other words, it's better to think of rvalue references as syntactic sugar than the actual code. The signature is different from A&& vs A& , but the argument type is not for function body purposes, it is always considered as A& with the intention of passing the transmitted object in some way, because const A& , although correctly syntactically, will not allow the desired behavior.
I can be very sure of this when I say that the compiler will generate the code body for f , as if it were declared f(A&) . According to the above, A&& helps the compiler in deciding when to allow the binding of a mutable reference to f , but otherwise the compiler does not consider the semantics of f(A&) and f(A&&) be different from what f returned.
This is a long way to say: the return f method is independent of the type of argument it receives.
Confusion is an elite. In fact, there are two instances when returning a value. First, a copy is created as temporary, then this temporary is assigned to something (or it is not and remains purely temporary). The second copy is most likely eliminated by optimizing the return. The first copy can be moved to g and cannot be located to f . I expect that in a situation where f cannot be undone, a copy will appear and then the transition from f to the source code.
To override this, the temporary must be explicitly built using std::move , that is, in the return statement in f . However, in g we return what is known to be temporary for the body of the function g , so it either moves twice or moves once, and then is deleted.
, , , . , / , , , , , ...