【问题标题】:Usage of std::forward vs std::movestd::forward 与 std::move 的用法
【发布时间】:2015-05-03 21:20:19
【问题描述】:

我总是读到std::forward 仅用于模板参数。然而,我在问自己为什么。请参阅以下示例:

void ImageView::setImage(const Image& image){
    _image = image;
}

void ImageView::setImage(Image&& image){
    _image = std::move(image);
}

这是两个基本相同的功能;一个采用左值引用,另一个采用右值引用。现在,我想既然std::forward 应该在参数是左值引用时返回左值引用,如果参数是 1 时返回右值引用,这段代码可以简化为:

void ImageView::setImage(Image&& image){
    _image = std::forward(image);
}

这有点类似于 cplusplus.com 提到的 std::forward 示例(只是没有任何模板参数)。我只是想知道,这是否正确,如果不正确,为什么。

我也在问自己到底有什么不同

void ImageView::setImage(Image& image){
    _image = std::forward(image);
}

【问题讨论】:

标签: c++ c++11 move forward


【解决方案1】:

不能在没有明确指定模板参数的情况下使用std::forward。它是有意在非推断上下文中使用的。

要理解这一点,您需要真正了解转发引用(T&& 用于推断的 T)在内部是如何工作的,而不是把它们当作“它的魔法”而挥之不去。那么让我们来看看。

template <class T>
void foo(T &&t)
{
  bar(std::forward<T>(t));
}

假设我们这样称呼foo

foo(42);
  • 42int 类型的右值。
  • T 推导出为int
  • 因此对bar 的调用使用int 作为std::forward 的模板参数。
  • std::forward&lt;U&gt; 的返回类型为U &amp;&amp;(在本例中为int &amp;&amp;),因此t 作为右值转发。

现在,让我们像这样调用foo

int i = 42;
foo(i);
  • iint 类型的左值。
  • 由于完美转发的特殊规则,当T &amp;&amp;类型的参数中使用V类型的左值推导T时,V &amp;用于推导。因此,在我们的例子中,T 被推断为int &amp;

因此,我们将int &amp; 指定为std::forward 的模板参数。因此,它的返回类型将是“int &amp; &amp;&amp;”,它会折叠为int &amp;。这是一个左值,所以i 被作为左值转发。

总结

为什么这适用于模板是当您使用std::forward&lt;T&gt; 时,T 有时是引用(当原始值是左值时)有时不是(当原始值是右值时)。因此,std::forward 将酌情转换为左值或右值引用。

您无法在非模板版本中进行这项工作,因为您只有一种类型可用。更不用说setImage(Image&amp;&amp; image) 根本不接受左值——左值不能绑定到右值引用。

【讨论】:

  • 好的,我想到目前为止我已经理解了。我想知道的是:我可以用左值引用和右值引用调用函数template &lt;typename T&gt; void foo(T&amp;&amp; t)?如果它是一个左值引用 T 被推断为V&amp;,这意味着参数类型变为(V&amp; &amp;&amp;),这基本上是一个对左值引用的 rvlaue 引用(或者什么?)?所以这是行为与非模板函数不同的一点,其中函数void foo(MyType&amp;&amp; t)只能用右值引用调用?
  • @user1488118 有引用折叠规则。 T &amp;&amp; &amp;&amp; 折叠为 T &amp;&amp;,所有其他(T &amp; &amp;&amp;T &amp;&amp; &amp;T &amp; &amp;)折叠为 T &amp;。 --- 是的,V&amp; 的推导允许您使用左值和右值(不一定是引用)调用函数模板。采用Image&amp;&amp; 的非模板函数只能使用Image 类型的右值调用。
  • foo(i) 会将T 推导出为intfoo(42) wouldn't compile,因为 42 是右值,不能绑定到非常量左值引用。使用V &amp; 而不是V 的特殊规则仅适用于模板参数T 的函数参数类型为T &amp;&amp; 的情况。其他任何情况都会导致规则不适用。
  • @user1488118 哪个“文档”? Cplusplus.com?该网站以其准确性而闻名,恰恰相反。首选cppreference.com。无论哪种方式,“文档”的唯一权威来源是标准,正如我在回答中所描述的那样指定它。
  • "std::forward 因此将酌情转换为右值引用的左值。"我认为您的意思是: std::forward 因此将酌情转换为左值或右值引用。我只是提出这个问题,因为我看到有人不明白它是如何工作的而感到困惑
【解决方案2】:

我推荐阅读 Scott Meyers 的“Effective Modern C++”,具体来说:

  • 第 23 项:了解std::movestd::forward
  • 第 24 条:区分右值引用的通用引用。

从纯技术角度来说,答案是肯定的:std::forward 可以做到这一切。 std::move 不是必需的。当然,这两种功能都没有 真的很有必要,因为我们可以到处写演员表,但我 希望我们同意那会很糟糕。 std::move的景点 方便、减少出错的可能性和更清晰

右值引用

这个函数接受右值,不能接受左值。

void ImageView::setImage(Image&& image){
    _image = std::forward(image);        // error 
    _image = std::move(image);           // conventional
    _image = std::forward<Image>(image); // unconventional
}

首先注意std::move 只需要一个函数参数,而std::forward 需要一个函数参数和一个模板类型参数。

通用引用(转发引用)

此函数接受所有并完美转发。

template <typename T> void ImageView::setImage(T&& image){
    _image = std::forward<T>(image);
}

【讨论】:

  • 注: “通用参考”的正式名称现在是转发参考
  • @JonathanWakely - 谢谢,我明白了。
  • @JonathanWakely:““通用参考”的正式名称现在是转发参考” - 我没有收到备忘录。从何时起?是在哪里公布的?谢谢。
  • @JohannGerell Herb Sutter 在 CppCon 2014 上也谈到了它,最新版本的 Effective C++ 也提到了它,IIRR。
  • 感谢参考,转发参考N4296的§14.8.2.3中定义
【解决方案3】:

您必须在std::forward 中指定模板类型。

在这种情况下,Image&amp;&amp; image始终是一个右值引用,std::forward&lt;Image&gt; 将始终移动,因此您不妨使用std::move

您的函数接受右值引用不能接受左值,因此它不等同于前两个函数。

【讨论】:

  • std::forward 仅在用作std::forward&lt;Image&gt;std::forward&lt;Image&amp;&amp;&gt; 时才会移动,而不是像您说的那样总是
  • @PiotrS。你是对的,我过于简单化了。希望那会更好。
猜你喜欢
  • 2016-07-08
  • 2016-03-21
  • 1970-01-01
  • 2013-06-25
  • 2018-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多