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);
42
是 int
类型的右值。
T
推导出为int
。
因此对bar
的调用使用int
作为std::forward
的模板参数。
std::forward<U>
的返回类型为U &&
(在本例中为int &&
),因此t
作为右值转发。
现在,让我们像这样调用foo
:
int i = 42;
foo(i);
i
是 int
类型的左值。
由于完美转发的特殊规则,当T &&
类型的参数中使用V
类型的左值推导T
时,V &
用于推导。因此,在我们的例子中,T
被推断为int &
。
因此,我们将int &
指定为std::forward
的模板参数。因此,它的返回类型将是“int & &&
”,它会折叠为int &
。这是一个左值,所以i
被作为左值转发。
总结
为什么这适用于模板是当您使用std::forward<T>
时,T
有时是引用(当原始值是左值时)有时不是(当原始值是右值时)。因此,std::forward
将酌情转换为左值或右值引用。
您无法在非模板版本中进行这项工作,因为您只有一种类型可用。更不用说setImage(Image&& image)
根本不接受左值——左值不能绑定到右值引用。
【讨论】:
好的,我想到目前为止我已经理解了。我想知道的是:我可以用左值引用和右值引用调用函数template <typename T> void foo(T&& t)
?如果它是一个左值引用 T 被推断为V&
,这意味着参数类型变为(V& &&)
,这基本上是一个对左值引用的 rvlaue 引用(或者什么?)?所以这是行为与非模板函数不同的一点,其中函数void foo(MyType&& t)
只能用右值引用调用?
@user1488118 有引用折叠规则。 T && &&
折叠为 T &&
,所有其他(T & &&
、T && &
和 T & &
)折叠为 T &
。 --- 是的,V&
的推导允许您使用左值和右值(不一定是引用)调用函数模板。采用Image&&
的非模板函数只能使用Image
类型的右值调用。
foo(i)
会将T
推导出为int
。 foo(42)
wouldn't compile,因为 42 是右值,不能绑定到非常量左值引用。使用V &
而不是V
的特殊规则仅适用于模板参数T
的函数参数类型为T &&
的情况。其他任何情况都会导致规则不适用。
@user1488118 哪个“文档”? Cplusplus.com?该网站以其准确性而闻名,恰恰相反。首选cppreference.com。无论哪种方式,“文档”的唯一权威来源是标准,正如我在回答中所描述的那样指定它。
"std::forward 因此将酌情转换为右值引用的左值。"我认为您的意思是: std::forward 因此将酌情转换为左值或右值引用。我只是提出这个问题,因为我看到有人不明白它是如何工作的而感到困惑【参考方案2】:
我推荐阅读 Scott Meyers 的“Effective Modern C++”,具体来说:
第 23 项:了解std::move
和std::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&& image
始终是一个右值引用,std::forward<Image>
将始终移动,因此您不妨使用std::move
。
您的函数接受右值引用不能接受左值,因此它不等同于前两个函数。
【讨论】:
std::forward
仅在用作std::forward<Image>
或std::forward<Image&&>
时才会移动,而不是像您说的那样总是
@PiotrS。你是对的,我过于简单化了。希望那会更好。以上是关于std::forward 与 std::move 的用法的主要内容,如果未能解决你的问题,请参考以下文章