7

以下代码使 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 也返回右值。

std:move 的魔力是什么?

谢谢

4

5 回答 5

10

std::move的参数看起来像是一个右值引用,这看起来确实令人困惑 - 为什么你可以调用move(str),什么时候str不是右值?

这里的技巧是右值引用在模板参数上的工作令人困惑:

如果模板参数Tint,那么T&&将是一个右值引用int&&
但是如果T是左值引用int&,那么T&&也将是左值引用int&

这是因为方式&&&结合:

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

所以当你调用move(str), Tis时std::string&,参数类型move<std::string&>也是std::string&- 一个左值引用,它允许函数调用编译。然后所move要做的就是将值转换为右值引用。

于 2012-08-30T09:25:47.040 回答
4

你可以认为std::move只是一个强制转换(但一个表达式强制转换,而不是一个类型强制转换)。该表达式std::move(x)是一个与 具有相同值的右值x,但即使它x本身是一个左值,它也可以工作。

在您的示例“code2”中,x确实是一个左值(类型为“对字符串的右值引用”)。此左值无法绑定到函数的返回类型(“对字符串的右值引用”),因此您需要将其显式转换为右值表达式。

我们也可以做相反的move,我通常称之为stay,它将右值变成左值(小心使用!):

template <typename T> T & stay(T && t) { return t; }

这主要适用于反常的单行者,或在酒吧给女孩留下深刻印象。

于 2012-08-30T09:07:06.713 回答
3

这里的人已经回答了这个问题,但我觉得需要更明确地说。人们经常与右值引用混淆的原因在于规则:

命名的右值引用是一个左值

起初令人困惑,这条规则是有道理的。右值引用的目标是绑定到您将不再需要的对象:无论是临时对象还是您知道永远不需要的对象,但编译器无法弄清楚。

命名的右值引用是您可以多次引用的东西:

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 

在这里,我在第一行创建了一个临时对象,但由于绑定到右值引用,我使它几乎像一个自动对象一样工作:我可以多次访问它。为了让最后一行工作,第二行不能编译。

什么std::move是将命名的右值引用(左值)更改为未命名的右值引用(右值)。

于 2012-08-30T10:03:40.003 回答
0

There are some articles to explain #1, but I don't understand why #2 also fails.

Remember, if it has a name, it's an lvalue. In your second case, x is still an lvalue even if it's an rvalue reference. std::move has the ability to turn lvalues into rvalues by doing nothing more than a static_cast<Type&&>(yourVar) on them. The resulting expression is an rvalue that can be accepted by any code requesting an rvalue reference. (a Type &&)

I'll illustrate with a few examples. In your original example, replace:

std::string str("xxx");
test1(str);

with

test1(string("xxx"));

There, the string object no longer has a name, it is now an rvalue and is accepted by test1.

How does move work? Again, quite simple. Again, replace your call with:

test1(std::move(str));

or with

test1(static_cast<std::string&&>(str));

Same thing basically, just that std::move better explains the intention.

Takeaways

  • if it has a name it's an lvalue

  • if it doesn't have a name, it's an rvalue and can be accepted wherever a Type && (an rvalue reference) is asked for.

  • You can static_cast an lvalue to an rvalue (but use std::move instead, that's what it's for), but you need to know what you're doing when you're doing that. (that is, use it on types that have move semantics implemented, I won't go into any detail here, I'll just link to a great article on the move semantics topic)

于 2012-08-30T09:50:41.347 回答
0

移动语义的魔力在于利用可破坏性。

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

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

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

于 2012-08-30T14:08:40.337 回答