Move 和 std: : forward 的区别是什么

我看到这个: 移动构造函数调用基类的移动构造函数

谁能解释一下:

  1. std::movestd::forward之间的区别,最好是一些代码示例?
  2. 如何轻松地思考它,什么时候使用哪一个
50205 次浏览

std::forward用于 前进的一个参数,就像它传递给函数的方式一样。如下所示:

何时使用 std: : 转发到转发参数?

使用 std::move提供一个对象作为 rvalue,以便可能匹配 move 构造函数或接受 rvalue 的函数。对于 std::move(x),即使 x本身不是一个右值,它也是这样做的。

std::forwardstd::move都只是石膏。

X x;
std::move(x);

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

std::move(make_X());

在本例中,它是一个标识函数: 获取类型为 X 的 rvalue 并返回类型为 X 的 rvalue。

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

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

将类型 X 的左值表达式 x转换为类型 Y 的表达式。Y 可以是什么有一些限制。

Y 可以是可访问的 X 的 Base,或者是对 X 的 Base 的引用。 Y 可以是 X,或者是对 X 的引用。不能用 forward去掉 cv 修饰符,但可以添加 cv 修饰符。Y 不能仅仅是从 X 可转换的类型,除非通过可访问的 Base 转换。

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

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

如果尝试将 Y 指定为不允许的内容,则将在编译时而不是运行时捕获错误。

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

std::forward只有一个用例: 将模板化的函数参数(在函数内部)强制转换为调用者用来传递它的值类别(lvalue 或 rvalue)。这允许将 rvalue 参数作为 rvalue 传递,将 lvalue 作为 lvalue 传递,这是一种称为“完美转发”的方案

说明:

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }


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 提到的,这两个函数也有相似之处,因为它们只是转换为引用类型。但是在这些特定的用例之外(这些用例覆盖了 rvalue 引用强制转换的99.9% 的有用性) ,您应该直接使用 static_cast,并对您正在做的事情做一个很好的解释。

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

我们从 std::move开始。

std::move

长话短说: std::move是为了将 什么都行转换成一个 rvalue (1) ,目的是让它看起来像一个临时值(即使它不是: std::move(non_temporary)) ,这样它的资源就可以从它那里被窃取,也就是说,感动可以从它那里被窃取(前提是 const属性不能阻止这一点; 是的,rvalue 可以是 const,在这种情况下你不能从它们那里窃取资源)。

std::move(x)表示 嗨,伙计们,请注意,我把这个 x给谁,可以使用和打破它分开,因为他喜欢,因此通常在右值引用参数上使用它,因为您确信它们绑定到临时参数。

这是 std::move的一个 C + + 14实现,与 Scott Meyers 在 高效的现代 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&&获取它的唯一参数,因此它可以对 lvalue 和 rvalue 进行操作; 相应地,T将被推导为 lvalue 引用或非引用类型;
  • 模板类型演绎 的位置,所以您不必通过 <…>指定模板参数,并且,在实践中,您永远不应该指定它;
  • 这也意味着 std::move只不过是一个 static_cast,模板参数根据非模板参数自动确定,其类型是推导出来的;
  • 通过对返回类型使用 rvalue 引用类型(而不是非引用类型) ,它在不复制任何副本的情况下使用 返回一个右值; 它是通过从 T中剥离任何引用性(通过 std::remove_reference_t) ,然后添加 &&来实现这一点的。

冷知识

你知道吗,除了我们正在谈论的 ABC1呼叫 std::move之外,还有另外一个?是的,它是 <utility>0,它做了一件几乎不相关的事情: 它是 <utility>1的一个版本,它不是将值从一个容器复制到另一个,它是 <utility>2,使用 <utility>中的 std::move; 所以它是 std::move,使用另一个 std::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;
  • 它通过 lvalue 引用获取它的唯一参数到一个没有引用的 T; 注意,由于 参考崩溃(参见 给你) ,std::remove_reference_t<T>&解析成与 T&解析成完全相同的东西; 然而..。
  • ... 使用 std::remove_reference_t<T>&而不是 T&的原因是 完全正确T放在 非推理的上下文中(参见 给你) ,因此是 < em > 禁用 模板类型推导,这样你就是 被迫的通过 <…>指定模板参数
  • 这也意味着 std::forward只不过是一个 static_cast,模板参数根据您必须传递给 std::forward的模板参数自动确定(通过引用折叠) ;
  • 通过对返回类型使用 rvalue 引用或者 lvalue 引用类型(而不是非引用类型) ,它在不复制任何副本的情况下实现了 返回一个右值或左值; 它依赖于应用于 T&&的引用折叠,其中 T是作为模板参数传递给 std::forward的参数: 如果 T是非引用,那么 T&&是 rvalue 引用,而如果 T是左值引用,那么 T&&也是左值引用。

高效的现代 C + + 的斯科特 · 迈耶斯(Scott Meyers)精确地说了以下几点:

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