什么是std的魔力:移动

时间:2012-08-30 08:49:05

标签: c++ c++11 rvalue-reference

以下代码使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);
    }
  1. move的参数仍然是右值引用,但move(str)没问题!
  2. move也会返回rvalue。
  3. std:move的神奇之处是什么?

    由于

5 个答案:

答案 0 :(得分:9)

std::move的参数看起来像是左值参考,这看起来确实令人困惑 - 为什么在move(str)不是右值时调用str

这里的技巧是rvalue引用在模板参数上容易混淆:

如果模板参数Tint,则T&&将为右值参考int&&
但如果T是左值参考int&,那么T&&也将是左值参考int&

这是因为&&&合并的方式:

Type & &&   ==  Type &
Type && &   ==  Type &
Type & &    ==  Type &
Type && &&  ==  Type &&

因此,当您致电move(str)时,Tstd::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 &&右值参考),就可以接受。

    < / LI>
  • 您可以static_cast 左值右值(但请使用std::move,这就是它的用途),但是你当你这样做时,需要知道你在做什么。 (也就是说,在实现了移动语义的类型上使用它,我不会在这里详细说明,我只会链接到move semantics topic上的一篇很棒的文章)

答案 4 :(得分:0)

移动语义的神奇之处在于利用可破坏性。

想象一个管理动态分配缓冲区(如std :: string)生命周期的类: 使用string const&的复制构造函数无法知道之后是否实际需要该参数,因此始终必须克隆此缓冲区,这可能是一项昂贵的操作。

如果我们可以(通过重载string&&的复制构造函数)知道参数是一次性的,我们就可以“窃取”不必复制的缓冲区(复杂表达式一遍又一遍)。

如前所述,右值引用是左值(它允许我们完全访问以便在上面的例子中窃取缓冲区)。现在假设我们想要使用参数 另一个类似的重载调用表达式:没有std::move这个参数对我们来说似乎很宝贵,而string const&重载已解决,而std::move允许我们 重复“移动技巧”(因为我们知道参数来自呼叫站点的右值)。