我很好奇这个功能是如何工作的。考虑像
这样的东西std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }
即使对于仅移动类型,此代码编译也很好,因为编译器会隐式移动它。但从逻辑上讲,对于任何返回表达式,确定结果是否引用局部变量都将解决暂停问题 - 如果编译器只是将所有局部变量视为返回表达式中的rvalues,那么这将成为变量的问题可以多次在该一个表达式中引用。即使本地只有一个直接引用,您也无法证明它没有其他间接别名。
那么编译器如何知道何时从返回表达式移动?
答案 0 :(得分:10)
有一个简单的规则:如果满足复制省略的条件(变量可能是函数参数除外),则视为右值。如果失败,则视为左值。否则,视为左值。
§12.8 [class.copy] p32
当满足或将满足复制操作的省略标准时,除了源对象是函数参数之外,并且要复制的对象由左值指定,重载决策要选择首先执行副本的构造函数,就像对象是由右值指定的一样。如果重载决策失败,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行此两阶段重载决策。如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数。 -end note ]
示例:
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
}
并非我个人同意该规则,因为以下代码中没有自动移动:
template<class T>
struct X{
T v;
};
template<class T>
T f(){
X<T> x;
return x.v; // no automatic move, needs 'std::move'
}
答案 1 :(得分:5)
标准没有说“引用局部变量”,但它说“是非易失性自动对象的名称”。
§12.8 [class.copy] p31
[...]在下列情况下,允许复制/移动操作的省略,称为复制省略:
- 在具有类返回类型的函数中的
return
语句中,当表达式是非易失性自动对象的名称时 [...]
因此,不允许使用指针或引用。如果你以邪恶的方式阅读它,试图利用不太可能有意的解释,你可以说它意味着移动以下
struct A {
std::unique_ptr<int> x;
std::unique_ptr<int> f() { return x; }
};
int main() { A a; a.f(); }
在这种情况下,返回表达式是具有自动存储持续时间的变量的名称。标准中的其他一些段落可以用多种方式解释,但规则是采取最有可能的解释。
答案 2 :(得分:3)
但从逻辑上讲,对于任何返回表达式,确定结果是否引用局部变量都将解决暂停问题。
您高估了问题的复杂性。很明显,编译器会查看返回表达式是否提到了一个本地变量,并且它必须跟踪所有这些变量以便无论如何都要调用析构函数。请注意,仅当返回明确提及变量时才会移动,如果返回指向本地变量的指针或引用,则不需要这样做:
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
}
答案 3 :(得分:2)