问题
I'm curious as to exactly how this feature works. Consider something like
std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }
This code compiles fine even for a move-only type, as the compiler implicitly moves it. But logically, for any return expression, determining whether or not the result refers to a local variable would be solving the Halting Problem- and if the compiler simply treated all local variables as rvalues in the return expression, then this would be problematic as the variable may be referred to in that one expression multiple times. Even if a local only had one direct reference, you would not be able to prove that it did not have other indirect aliases.
So how does the compiler know when to move from the return expression?
回答1:
There's a simple rule: If the conditions for copy elision are met (except that the variable may be function parameter), treat as rvalue. If that fails, treat as lvalue. Otherwise, treat as lvalue.
§12.8 [class.copy] p32
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]
Example:
template<class T>
T f(T v, bool b){
T t;
if(b)
return t; // automatic move
return v; // automatic move, even though it's a parameter
}
Not that I personally agree with that rule, since there is no automatic move in the following code:
template<class T>
struct X{
T v;
};
template<class T>
T f(){
X<T> x;
return x.v; // no automatic move, needs 'std::move'
}
See also this question of mine.
回答2:
The Standard does not say "refers to a local variable", but it says "is the name of a non-volatile automatic object".
§12.8 [class.copy] p31
[...] This elision of copy/move operations, called copy elision, is permitted in the following circumstances [...]:
- in a
return
statement in a function with a class return type, when the expression is the name of a non-volatile automatic object [...]
So, pointers or references are not allowed. If you read it in evil ways that seek to exploit an interpretation that is less likely to have been intended, you can say that it means to move the following
struct A {
std::unique_ptr<int> x;
std::unique_ptr<int> f() { return x; }
};
int main() { A a; a.f(); }
In this case, the return expression is the name of a variable with automatic storage duration. Some other paragraphs in the Standard can be interpreted in multiple ways, but the rule is to take the interpretation that is most likely to be intended.
回答3:
But logically, for any return expression, determining whether or not the result refers to a local variable would be solving the Halting Problem.
You are overestimating the complexity of the problem. Clearly the compiler sees if the return expression mentions one of the variables that is local, and it must keep track of all those variables to call the destructors anyway. Note that it will move only if the return mentions explicitly the variable, if you return a pointer or reference to a local variable it need not do so:
std::unique_ptr<int>& same( std::unique_ptr<int>& x ) { return x; }
std::unique_ptr<int> foo() {
std::unique_ptr<int> p( new int );
std::unique_ptr<int>& r = same(p);
return r; // FAIL
}
回答4:
I think you overestimate the capacity of the compiler here.
If you directly return a local variable, then the job is easy: you can move it.
If you are calling an expression that requires to move from a local variable, then you must specify it manually.
See some examples here.
来源:https://stackoverflow.com/questions/11492948/how-does-the-compiler-know-to-move-local-variables