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

时间:2012-03-12 17:20:53

标签: c++ c++11 perfect-forwarding

我在这里看到了这个: Move Constructor calling base-class Move Constructor

有人可以解释一下:

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

4 个答案:

答案 0 :(得分:141)

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

std::forward有一个用例:将模板化函数参数(在函数内)转换为调用者用于传递它的值类别(左值或右值)。这允许rvalue参数作为rvalues传递,并且lvalues作为lvalues传递,这个方案称为“完美转发”。

illustrate

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

正如霍华德所提到的那样,这些函数只是简单地转换为引用类型也有相似之处。但是在这些特定用例之外(它覆盖了右值参考演员的99.9%的有用性),你应该直接使用static_cast并写下你正在做的事情的一个很好的解释。

答案 1 :(得分:55)

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的约束有限制。

Y可以是X的可访问基数,也可以是对X的基数的引用.Y可以是X,也可以是对X的引用。不能用forward抛弃cv限定符,但可以添加cv-qualifiers。 Y不能是只能从X转换的类型,除非通过可访问的Base转换。

如果Y是左值引用,则结果将是左值表达式。如果Y不是左值引用,则结果将是rvalue(准确的xvalue)表达式。

仅当Y不是左值引用时,

forward才能获取rvalue参数。也就是说,你不能将左值转换为左值。这是出于安全原因,因为这样做通常会导致悬挂参考。但是向rvalue投射一个右值是可以的,并允许。

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

答案 2 :(得分:20)

std::forward用于转发参数,与传递给函数的方式完全相同。就像这里显示的那样:

When to use std::forward to forward arguments?

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

答案 3 :(得分:7)

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

我将从 std::move 开始,早在理解 std::forward 之前我就真正理解了它。

std::move

长话短说:std::move 用于将 anything 转换为右值,目的是使其看起来像一个临时值(即使它不是:std::move(non_temporary) ),这样它的资源就可以从中窃取(即移动)。

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,模板参数根据非模板参数自动确定,其类型是推导出来的;
  • 它通过使用右值引用类型(而不是非引用类型)作为返回类型,在不进行任何复制的情况下返回一个右值;它通过从 T 中通过 std::remove_reference_t 去除任何引用,然后添加 && 来实现。

琐事

您知道吗,除了我们正在谈论的 std::move from <utility> 之外,还有另一个?是的,它是 std::move from <algorithm>,它做了一件几乎不相关的事情:它是 std::copy 的一个版本,它不是将值从一个容器复制到另一个容器,而是移动它们,使用 { {1}} 来自 std::move;所以它是一个使用另一个 <utility>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)); } 的 C++14 实现:

std::forward

由此我们可以观察到关于 template<typename T> T&& forward(std::remove_reference_t<T>& t) { return static_cast<T&&>(t); } 的以下内容:

  • 它是一个模板函数,所以它适用于任何类型的 std::forward;
  • 它通过对无引用 T 的左值引用来获取其唯一参数;请注意,由于 Reference collapsing(参见 here),T 解析为与 std::remove_reference_t<T>& 解析为完全相同的内容;然而...
  • ...使用T&而不是std::remove_reference_t<T>&的原因正是T&放入非推导上下文(参见here),因此禁用模板类型推导,以便您被迫通过 T
  • 指定模板参数
  • 这也意味着 <…> 只不过是一个 std::forward,模板参数根据您必须传递给 static_cast 的模板参数自动确定(通过引用折叠);< /li>
  • 它通过使用右值引用或左值引用类型(而不是非引用类型)作为返回类型,在不进行任何复制的情况下返回右值或左值;它通过依赖应用于 std::forward 的引用折叠来实现,其中 T&& 是您作为模板参数传递给 T 的那个:如果 std::forward 是非引用,那么 T 是一个右值引用,而如果 T&& 是一个左值引用,那么 T 也是一个左值引用。