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

Usage of std::forward vs std::move

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

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

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

这是两个基本相同的函数;一个采用左值引用,另一个采用右值引用。现在,我认为如果参数是左值引用,std::forward 应该是左值引用,如果参数是一个,则应该是右值引用,这段代码可以简化为像这样:

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

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

我也在问自己

到底有什么不同
void ImageView::setImage(Image& image){
    _image = std::forward(image);
}

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

在此上下文中,Image&& image 始终 r 值参考,std::forward<Image> 将始终移动,因此您不妨使用 std::move .

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

不能使用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<U> 的 return 类型是 U &&(在本例中是 int &&),因此 t 被作为右值转发。

现在,我们这样调用 foo

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

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

总结

为什么这适用于模板是因为当您执行 std::forward<T> 时,T 有时是引用(当原始值是左值时)有时不是(当原始值是右值时)。 std::forward 因此将根据需要转换为左值或右值引用。

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

我推荐阅读 Scott Meyers 的“Effective Modern C++”,特别是:

  • 项目 23:理解 std::movestd::forward
  • 项目24:区分通用引用为右值引用。

From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky. std::move’s attractions are convenience, reduced likelihood of error, and greater clarity.

右值参考

此函数接受右值,不能接受左值。

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