以下代码使VC2010失败:
//code1
std::string& test1(std::string&& x){
return x;
}
std::string str("xxx");
test1(str); //#1 You cannot bind an lvalue to an rvalue reference
//code2
std::string&& test1(std::string&& x){
return x; //#2 You cannot bind an lvalue to an rvalue reference
}
有一些文章可以解释#1,但我不明白为什么#2也会失败。
让我们看看std :: move如何实现
template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}
std:move的神奇之处是什么?
由于
答案 0 :(得分:9)
std::move
的参数看起来像是左值参考,这看起来确实令人困惑 - 为什么在move(str)
不是右值时调用str
?
这里的技巧是rvalue引用在模板参数上容易混淆:
如果模板参数T
为int
,则T&&
将为右值参考int&&
。
但如果T
是左值参考int&
,那么T&&
也将是左值参考int&
。
这是因为&
和&&
合并的方式:
Type & && == Type &
Type && & == Type &
Type & & == Type &
Type && && == Type &&
因此,当您致电move(str)
时,T
为std::string&
,参数类型move<std::string&>
也为std::string&
- 左值参考,允许函数调用编译。然后,move
所要做的就是将值转换为右值引用。
答案 1 :(得分:4)
您可以将std::move
视为演员表(但表达式演员表,而非演员表)。表达式std::move(x)
是一个与x
具有相同值的右值,但即使x
本身是左值,它也能正常工作。
在您的示例“code2”中,x
确实是一个左值(类型为“对字符串的rvalue引用”)。此左值不能绑定到函数的返回类型(“对字符串的rvalue引用”),因此您需要将其显式转换为右值表达式。
我们也可以与move
相反,我通常称之为stay
,它将rvalues转换为左值(小心使用!):
template <typename T> T & stay(T && t) { return t; }
这主要适用于perverse one-liners,或者让女孩在酒吧留下深刻印象。
答案 2 :(得分:2)
这里的人已经回答了这个问题,但我觉得需要更明确地说出来。人们经常与右值引用混淆的原因在于规则:
命名的右值引用是左值。
起初感到困惑,这个规则是有道理的。右值引用的目标是绑定到您不再需要的对象:要么是临时的,要么是您知道永远不需要的对象,但编译器无法弄清楚它。
一个命名的右值参考是你可以多次引用的东西:
std::unique_ptr<int> && rref = std::unique_ptr<int>{ new int{1} };
std::unique_ptr<int> p2{rref}; // if it worked...
rref->use(); // this would crash
在这里,我在第一行创建了一个临时文件,但是由于绑定到rvalue引用,我使它的工作方式几乎像一个自动对象:我可以多次访问它。并且为了使最后一行起作用,第二行不能编译。
std::move
做的是将命名的右值引用(左值)更改为未命名的右值引用(右值)。
答案 3 :(得分:0)
有一些文章要解释#1,但我不明白为什么#2 也失败了。
请记住,如果它有名称,则为左值。
在第二种情况下,x仍然是左值,即使它是右值参考。 std::move
能够将左值转换为右值,只需对其进行static_cast<Type&&>(yourVar)
。结果表达式是一个rvalue,可以被请求右值引用的任何代码接受。 (a Type &&
)
我将用几个例子来说明。在原始示例中,替换:
std::string str("xxx");
test1(str);
与
test1(string("xxx"));
在那里,字符串对象不再具有名称,现在是右值,并且被test1接受。
移动是如何工作的?再说一遍,非常简单。再次,用以下方式替换您的电话:
test1(std::move(str));
或
test1(static_cast<std::string&&>(str));
基本相同,只是std :: move更好地解释了意图。
<强>外卖强>
如果有名称,则为左值
如果它没有名称,则它是一个左值,只要要求Type &&
(右值参考),就可以接受。
您可以static_cast
左值到右值(但请使用std::move
,这就是它的用途),但是你当你这样做时,需要知道你在做什么。 (也就是说,在实现了移动语义的类型上使用它,我不会在这里详细说明,我只会链接到move semantics topic上的一篇很棒的文章)
答案 4 :(得分:0)
移动语义的神奇之处在于利用可破坏性。
想象一个管理动态分配缓冲区(如std :: string)生命周期的类:
使用string const&
的复制构造函数无法知道之后是否实际需要该参数,因此始终必须克隆此缓冲区,这可能是一项昂贵的操作。
如果我们可以(通过重载string&&
的复制构造函数)知道参数是一次性的,我们就可以“窃取”不必复制的缓冲区(复杂表达式一遍又一遍)。
如前所述,右值引用是左值(它允许我们完全访问以便在上面的例子中窃取缓冲区)。现在假设我们想要使用参数
另一个类似的重载调用表达式:没有std::move
这个参数对我们来说似乎很宝贵,而string const&
重载已解决,而std::move
允许我们
重复“移动技巧”(因为我们知道参数来自呼叫站点的右值)。