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

What's the difference between std::move and std::forward

本文关键字:std 什么 区别 move forward      更新时间:2023-10-16

我看到这里:移动构造函数调用基类Move Constructor

谁能解释一下:

  1. std::movestd::forward之间的差异,最好有一些代码示例?
  2. 如何轻松思考,以及何时使用哪个

std::move接受一个对象,并允许您将其视为临时(右值)。虽然这不是语义上的要求,但通常接受右值引用的函数将使其无效。当您看到std::move时,表示之后不应该使用对象的值,但是您仍然可以分配一个新值并继续使用它。

std::forward有一个用例:将模板化的函数参数(在函数内部)强制转换为调用者用来传递它的值类别(左值或右值)。这允许将右值参数作为右值传递,将左值作为左值传递,这种模式称为"完美转发"。

游戏介绍:

void overloaded( int const &arg ) { std::cout << "by lvaluen"; }
void overloaded( int && arg ) { std::cout << "by rvaluen"; }
template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
(for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
std::cout << "via std::forward: ";
overloaded( std::forward< t >( arg ) );
std::cout << "via std::move: ";
overloaded( std::move( arg ) ); // conceptually this would invalidate arg
std::cout << "by simple passing: ";
overloaded( arg );
}
int main() {
std::cout << "initial caller passes rvalue:n";
forwarding( 5 );
std::cout << "initial caller passes lvalue:n";
int x = 5;
forwarding( x );
}

正如Howard提到的,这两个函数也有相似之处,因为它们都只是转换为引用类型。但是在这些特定的用例之外(这些用例覆盖了99.9%的右值引用强制转换的有用性),你应该直接使用static_cast,并写一个很好的解释你在做什么。

std::forwardstd::move都是类型转换。

X x;
std::move(x);

上面的语句将X类型的左值表达式x转换为X类型的右值表达式(确切地说是xvalue)。move也可以接受右值:

std::move(make_X());

,在这种情况下,它是一个恒等函数:接受X类型的右值并返回X类型的右值

使用std::forward,您可以在一定程度上选择目的地:

X x;
std::forward<Y>(x);

将类型为X的左值表达式x强制转换为类型为Y的表达式。

Y可以是可访问的X的Base,也可以是对X的Base的引用。Y可以是X,也可以是对X的引用。Y不能仅仅是X的可转换类型,除非通过可访问的Base转换。

如果Y是一个左值引用,结果将是一个左值表达式。如果Y不是左值引用,则结果将是一个右值表达式(准确地说是xvalue)。

forward只有在Y不是左值引用时才能接受右值实参。也就是说,不能将右值强制转换为左值。这是出于安全考虑,因为这样做通常会导致悬空引用。但是将右值转换为右值是可以的,也是允许的。

如果你试图将Y指定为不允许的东西,错误将在编译时被捕获,而不是运行时。

std::forward用于转发参数,与传递给函数的方式完全相同。如下所示:

何时使用std::forward来转发参数?

使用std::move提供了一个对象作为右值,可能匹配移动构造函数或接受右值的函数。即使x本身不是右值,它也会对std::move(x)执行此操作。

我认为比较两个示例实现可以提供很多关于它们的目的和它们的不同之处的见解。

让我们从std::move开始。

std::move

长话短说:std::move是为了把任何变成右值(¹),目的是使它看起来像一个临时的(即使它不是:std::move(non_temporary)),所以它的资源可以从它窃取,即移动从它(只要这不是由const属性阻止;是的,右值可以是const,在这种情况下,你不能从他们那里窃取资源)。

std::move(x)嗨,伙计们,请注意我给这个x的人可以使用和分解它,因为他喜欢,所以你通常在右值引用参数上使用它,因为你确定它们被绑定到临时值。

这是std::move的c++ 14实现,与Scott Meyers在Effective Modern c++中展示的非常相似(在书中返回类型std::remove_reference_t<T>&&被更改为decltype(auto),这是从return语句中推断出来的)

template<typename T>
std::remove_reference_t<T>&& move(T&& t) {
return static_cast<std::remove_reference_t<T>&&>(t);
}

由此我们可以观察到以下关于std::move的情况:

  • 它是一个模板函数,所以它适用于任何类型T;
  • 通过通用(或转发)引用T&&获取唯一参数,因此它可以对左值和右值进行操作;T将相应地被推断为左值引用或非引用类型;
  • 模板类型演绎就位,所以你不必通过<…>指定模板参数,而且,在实践中,你不应该指定它;
  • 这也意味着std::move只不过是一个static_cast,它的模板参数是根据非模板参数自动确定的,非模板参数的类型是推断出来的;
  • it返回右值不做任何复制,通过使用右值引用类型(而不是非引用类型)作为返回类型;它是这样做的:从T中剥离任何引用,通过std::remove_reference_t,然后添加&&

琐事

你知道吗,除了我们正在讨论的<utility>中的std::move,还有另一个?是的,这是<algorithm>std::move,这是一个几乎不相关的事情:它是std::copy的一个版本,而不是将值从一个容器复制到另一个容器,它移动它们,使用<utility>std::move;所以它是一个使用另一个std::movestd::move!

std::forward

长话短说:std::forward用于将函数内部的参数转发给另一个函数,同时告诉后一个函数是否使用临时函数调用了前一个函数。

std::forward<X>(x)表示以下两种情况之一:

  • (如果x绑定到右值,即临时的)你好,函数先生,我已经从另一个函数那里收到了这个包裹,这个函数在你使用它之后不需要它,所以请随意使用它;
  • (如果x被绑定到左值,即非临时的)嗨,函数先生,我从另一个函数那里收到了这个包裹,这个函数在你使用它之后确实需要它,所以请不要破坏它

所以你通常在转发/通用引用时使用它,因为它们可以绑定到临时和非临时。

换句话说,std::forward是为了能够把这个代码

template<typename T>
void wrapper(T&& /* univ. ref.: it binds to lvalues as well as rvalues (temporaries)*/ t) {
// here `t` is an lvalue, so it doesn't know whether it is bound to a temporary;
// `T` encodes this missing info, but sadly we're not making `some_func` aware of it,
// therefore `some_func` will not be able to steal resources from `t` if `t`
// is bound to a temporary, because it has to leave lvalues intact
some_func(t);
}

template<typename T>
void wrapper(T&& /* univ. ref.: it binds to lvalues as well as rvalues (temporaries)*/ t) {
// here `t` is an lvalue, so it doesn't know whether it is bound to a temporary;
// `T` encodes this missing info, and we do use it:
// `t` bound to lvalue => `T` is lvalue ref => `std::forward` forwards `t` as lvalue
// `t` bound to rvalue => `T` is non-ref    => `std::forward` turns `t` into rvalue
some_func(std::forward<T>(t));
}

这是同一本书中std::forward的c++ 14实现:

template<typename T>
T&& forward(std::remove_reference_t<T>& t) {
return static_cast<T&&>(t);
}

由此我们可以观察到以下关于std::forward:

  • 它是一个模板函数,所以它适用于任何类型T;
  • 通过左值引用获取唯一的参数,指向没有引用的T;请注意,由于引用折叠(见这里),std::remove_reference_t<T>&解析的内容与T&解析的内容完全相同;然而…
  • …使用std::remove_reference_t<T>&而不是T&的原因是正是T放在非演绎的上下文中(见这里),因此禁用模板类型演绎,以便强制通过<…>
  • 指定模板参数。
  • 这也意味着std::forward只不过是一个static_cast,它的模板参数是根据必须传递给std::forward的模板参数自动确定的(通过引用折叠);
  • it返回右值或左值不进行任何复制,通过使用右值引用或左值引用类型(而不是非引用类型)作为返回类型;它通过依赖于应用于T&&的引用崩溃来实现,其中T是您作为模板参数传递给std::forward的一个:如果T是非引用,那么T&&是右值引用,而如果T是左值引用,那么T&&也是左值引用;
  • 有些人写static_cast<T&&>而不是std::forward<T>,因为这就是CC_77,他们保存了一个安装,代价是代码不太清晰,并且可能无法捕获错误。

¹Scott Meyers在Effective Modern c++中说了下面的话:

std::move无条件地将其参数转换为右值