std::forward 与 std::move 的用法

Posted

技术标签:

【中文标题】std::forward 与 std::move 的用法【英文标题】:Usage of std::forward vs 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);

【问题讨论】:

要真正了解,请观看此视频:channel9.msdn.com/Shows/Going+Deep/… 问题:Forward not 根据其参数返回左值引用或右值引用。实际上,它完全忽略了参数的值类别。 这能回答你的问题吗? What's the difference between std::move and std::forward 当它不是模板参数时,&& 表示右值(而不是转发引用),因此遵循binding rules (see the table here) 这意味着对Image 的左值引用不能绑定到@987654331 @. 【参考方案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。你是对的,我过于简单化了。希望那会更好。

以上是关于std::forward 与 std::move 的用法的主要内容,如果未能解决你的问题,请参考以下文章

std::move与std::forward

std::move与std::forward

C++的std::move与std::forward原理大白话总结

理解std::move和std::forward

std::move()和std::forward()

std :: move和std :: forward之间有什么区别