182

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

有人可以解释一下:

  1. 和 之间的区别std::movestd::forward最好是一些代码示例?
  2. 如何轻松思考,何时使用 which
4

4 回答 4

169

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

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

为了说明

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直接使用并为您正在做的事情写一个很好的解释。

于 2012-03-15T09:08:36.523 回答
63

两者std::forwardstd::move只是演员表。

X x;
std::move(x);

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

std::move(make_X());

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

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

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

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

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

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

forward仅当 Y 不是左值引用时才能采用右值参数。也就是说,您不能将右值转换为左值。这是出于安全原因,因为这样做通常会导致引用悬空。但是将右值转换为右值是可以的并且是允许的。

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

于 2012-03-12T17:52:06.427 回答
21

std::forward用于完全按照传递给函数的方式转发参数。就像这里显示的:

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

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

于 2012-03-12T17:27:59.157 回答
14

我认为比较两个示例实现可以提供很多关于它们的用途以及它们之间的区别的见解。

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

std::move

长话短说:std::move用于将任何东西变成右值(¹),目的是使它看起来像一个临时的(即使它不是:)std::move(non_temporary),以便可以从中窃取其资源,即从中移出(前提是const属性没有阻止这一点;是的,右值可以是const,在这种情况下,您不能从它们那里窃取资源)。

std::move(x),伙计们,请注意,我将其提供给x的人可以根据自己的喜好使用和分解它,因此您通常在右值引用参数上使用它,因为您确定它们绑定到临时变量。

这是一个 C++14 实现,与 Scott Meyers 在Effective Modern C++std::move中展示的非常相似(在书中,返回类型更改为,从语句中推导出来)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将相应地推导出为左值引用或非引用类型;
  • 模板类型推导已经到位,因此您不必通过 指定模板参数<…&gt;,并且在实践中,您永远不应该指定它;
  • 这也意味着std::move无非是一个static_cast模板参数根据非模板参数自动确定,其类型是推导出来的;
  • 通过使用右值引用类型(而不是非引用类型)作为返回类型,它返回一个右值而不进行任何复制;它通过从T、 via中剥离任何引用std::remove_reference_t,然后添加&&.

琐事

你知道吗,除了我们正在谈论的std::move那个<utility>,还有另一个?是的,它是std::movefrom<algorithm>,它做了一件几乎不相关的事情:它是一个版本,std::copy它不是将值从一个容器复制到另一个容器,而是使用from移动它们;所以它是一个使用另一个的。std::move<utility>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;请注意,由于Reference collapsing(请参见此处),std::remove_reference_t<T>&解析到与解析到的完全相同的事物T&;然而...
  • ...std::remove_reference_t<T>&使用而不是的T&原因正是为了放入T推导上下文(参见此处),从而禁用模板类型推导,因此您被迫通过指定模板参数<…&gt;
  • 这也意味着这std::forward只不过是static_cast根据您必须传递给的模板参数自动确定的模板参数(通过引用折叠)std::forward
  • 它通过使用右值引用或左值引用类型(而不是非引用类型)作为返回类型来返回右值或左值而不进行任何复制;它通过依赖引用折叠来做到这一点T&&,其中T是您作为模板参数传递给的std::forward那个:如果这T是一个非引用,那么T&&是一个右值引用,而如果T是一个左值引用,那么T&&也是一个左值引用.

¹有效现代 C++中的 Scott Meyers 准确地说如下:

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

于 2020-12-25T09:14:57.290 回答