std::move 和 std::forward 有什么区别
What's the difference between std::move and std::forward
我看到这里:移动构造函数调用基类Move Constructor
谁能解释一下:
std::move
和std::forward
之间的差异,最好有一些代码示例?- 如何轻松思考,以及何时使用哪个
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::forward
和std::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::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
; - 通过左值引用获取唯一的参数,指向没有引用的
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
无条件地将其参数转换为右值
- 这个语法std::class<>{}(arg1, arg2) 在C++中是什么意思?
- "using namespace std;"在C++的作用是什么?
- 传递给std::function template的template参数究竟代表什么
- "std::unique_XXX"命名约定背后的基本原理是什么?
- 什么是 std::exception::what() 以及为什么要使用它?
- C++如果必须在编译时确定大小,std::array 有什么意义?
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?
- 当 std::move 与 C 样式数组或不移动对象时会发生什么
- 什么是自动 t1=std::make_tuple(case1==case2,整数)的值
- C++std::atomic在程序员级别保证了什么
- 当为可变性配置时,boost::heap::d_ary_heap 保留的额外 std::list 的目的是什么?
- 引用 std::any 或 not_yet_in_std::whatever 的惯用方式是什么?
- 在什么条件下使用 std::memcpy 在对象之间复制是安全的?
- 在自定义 std::vector-like 容器中处理指针和非指针模板类型的最佳方法是什么?
- 在 C# 中等效的 std::rotate() 是什么?
- 什么是 std::function::argument_type 的替代品?
- 什么是 std::jthread 在 c++20 中?
- std::enable_if 和 std::enable_if_t 有什么区别?
- "owned pointer"和 std::shared_ptr 的"stored pointer"有什么区别?