【问题标题】:How does std::move() transfer values into RValues?std::move() 如何将值传输到 RValues 中?
【发布时间】:2011-11-22 12:52:16
【问题描述】:

我发现自己没有完全理解std::move() 的逻辑。

起初,我用谷歌搜索了它,但似乎只有关于如何使用 std::move() 的文档,而不是它的结构如何工作。

我的意思是,我知道模板成员函数是什么,但是当我查看 VS2010 中的 std::move() 定义时,它仍然令人困惑。

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);
    }

首先对我来说很奇怪的是参数 (_Ty&& _Arg),因为当我像下面这样调用函数时,

// main()
Object obj1;
Object obj2 = std::move(obj1);

基本上等于

// std::move()
_Ty&& _Arg = Obj1;

但是正如您已经知道的,您不能直接将 LValue 链接到 RValue 引用,这让我认为它应该是这样的。

_Ty&& _Arg = (Object&&)obj1;

然而,这是荒谬的,因为 std::move() 必须适用于所有值。

所以我想要完全理解它是如何工作的,我也应该看看这些结构。

template<class _Ty>
struct _Remove_reference
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&>
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&&>
{   // remove rvalue reference
    typedef _Ty _Type;
};

不幸的是,它仍然令人困惑,我不明白。

我知道这都是因为我缺乏关于 C++ 的基本语法技能。 我想知道这些是如何彻底工作的,我可以在互联网上获得的任何文件都将受到欢迎。 (如果你能解释一下,那也太棒了)。

【问题讨论】:

  • @NicolBolas 相反,问题本身表明 OP 在该声明中低估了自己。正是 OP 对基本语法技能的掌握,他们甚至可以提出问题。
  • 我不同意——你可以快速学习或者已经很好地掌握了计算机科学或整个编程,即使是在 C++ 中,但仍然在语法上挣扎。最好花时间学习机制、算法等,但根据需要依赖参考来复习语法,而不是在技术上表达清楚但对复制/移动/前进等内容有浅薄的理解。在您知道 move 的作用之前,您应该如何知道“何时以及如何使用 std::move 来构建某些东西”?
  • 我想我来这里是为了了解move 的工作原理,而不是它的实现方式。我觉得这个解释非常有用:pagefault.blog/2018/03/01/….

标签: c++ c++11 move-semantics


【解决方案1】:

我们从 move 函数开始(我稍微清理了一下):

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

让我们从更简单的部分开始——即使用右值调用函数时:

Object a = std::move(Object());
// Object() is temporary, which is prvalue

我们的move 模板被实例化如下:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

由于remove_referenceT&amp; 转换为TT&amp;&amp; 转换为T,并且Object 不是引用,所以我们的最终函数是:

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

现在,您可能想知道:我们还需要演员表吗?答案是:是的,我们愿意。原因很简单;命名的右值引用视为左值(标准禁止从左值到右值引用的隐式转换)。


当我们使用左值调用 move 时会发生以下情况:

Object a; // a is lvalue
Object b = std::move(a);

以及对应的move实例化:

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

同样,remove_referenceObject&amp; 转换为 Object,我们得到:

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

现在我们进入棘手的部分:Object&amp; &amp;&amp; 是什么意思,它如何绑定到左值?

为了实现完美转发,C++11标准提供了引用折叠的特殊规则,如下:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

如您所见,在这些规则下,Object&amp; &amp;&amp; 实际上意味着Object&amp;,这是一个允许绑定左值的普通左值引用。

最终的功能是:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

这与之前使用 rvalue 的实例化没有什么不同——它们都将其参数转换为 rvalue 引用,然后返回它。不同之处在于第一个实例化只能与右值一起使用,而第二个实例化与左值一起使用。


为了解释为什么我们需要更多remove_reference,让我们试试这个函数

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

并用左值实例化它。

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

应用上面提到的引用折叠规则,你可以看到我们得到了不可用的函数move(简单地说,你用左值调用它,你得到左值)。如果有的话,这个函数就是恒等函数。

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}

【讨论】:

  • 不错的答案。虽然我知道对于左值,将T 评估为Object&amp; 是一个好主意,但我不知道这真的完成了。在这种情况下,我本来希望 T 也评估为 Object,因为我认为这是引入包装器引用和 std::ref 的原因,或者不是。
  • template &lt;typename T&gt; void f(T arg)(维基百科文章中的内容)和template &lt;typename T&gt; void f(T&amp; arg) 之间存在差异。第一个解析为值(如果要传递引用,则必须将其包装在std::ref 中),而第二个始终解析为引用。可悲的是,模板参数推导的规则相当复杂,所以我无法提供精确的推理为什么 T&amp;&amp; resovles 到 Object&amp; &amp;&amp;(但确实发生了)。
  • 但是,这种直接方法是否行不通? template &lt;typename T&gt; T&amp;&amp; also_wanna_be_move(T&amp; arg) { return static_cast&lt;T&amp;&amp;&gt;(arg); }
  • 这解释了为什么 remove_reference 是必要的,但我仍然不明白为什么该函数必须采用 T&&(而不是 T&)。如果我正确理解了这个解释,那么无论您得到 T&& 还是 T& 都无关紧要,因为无论哪种方式,remove_reference 都会将其转换为 T,然后您将添加回 &&。那么为什么不说你接受一个 T& (这在语义上就是你所接受的),而不是 T&& 并依靠完美的转发来允许调用者传递一个 T&?
  • @mgiuca:如果您希望 std::move 仅将左值转换为右值,那么是的,T&amp; 可以。这个技巧主要是为了灵活性:您可以在所有内容(包括右值)上调用 std::move 并取回右值。
【解决方案2】:

_Ty 是模板参数,在这种情况下

Object obj1;
Object obj2 = std::move(obj1);

_Ty 是类型“对象 &”

这就是需要 _Remove_reference 的原因。

会更像

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

如果我们不删除引用,那就像我们正在做的那样

Object&& obj2 = (ObjectRef&&)obj1_ref;

但是 ObjectRef&& 简化为 Object &,我们无法绑定到 obj2。

减少这种方式的原因是为了支持完美转发。见this paper

【讨论】:

  • 这并不能解释为什么_Remove_reference_ 是必要的。例如,如果你有 Object&amp; 作为 typedef,并且你引用它,你仍然会得到 Object&amp;。为什么这不适用于 &&?对此一个答案,它与完美转发有关。
  • 是的。答案很有趣。 A & && 被简化为 A &,所以如果我们尝试使用 (ObjectRef &&)obj1_ref,在这种情况下我们会得到 Object &。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-04
  • 2013-05-18
  • 2016-09-10
  • 2022-01-21
  • 2021-11-06
  • 1970-01-01
  • 2019-01-13
相关资源
最近更新 更多