std::move
不会移动任何东西,std::forward
不会传递任何东西。二者在运行时什么都不会做。std::move
和std::forward
只是执行类型转换(cast)的函数(模板)。std::move
无条件地将参数转换为右值,而std::forward
只有当满足某个条件时才做如此转换。就这样。- 看看
std::move
的实现:
// GCC版本,MSVC与之区别仅在于将 remove_reference<_Tp>::type换成了remove_reference_t<_Ty>
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
-
std::move
接受一个对象的万能引用参数(见Item 24),并返回该对象的&&
引用。如Item 28所解释,如果T
是一个左值引用,那么T&&
也会是左值引用。为了避免这种情况,先对T
使用std::remove_reference
,这样能保证返回的是右值引用。也有提议认为可以将其命名为rvalue_cast
。 -
下面看一个例子。假设有一个类
Annotation
,其中有一个成员std::string value
,在构造时由一个传入的参数text
构造。由于构造函数只需要读取而不用修改text
的值,你自然地考虑使用const
修饰符:
class Annotation {
public:
explicit Annotation(const std::string text) // 为何以值传递,见Item 41
…
};
又考虑到想避免复制 text
的开销,你使用了 std::move
来将 text
转换为右值以构造 value
:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) // "move" text into value; this code
{ … } // doesn't do what it seems to!
…
private:
std::string value;
};
以上代码可以通过编译、链接,也能正常运行,将 text
的值传给 value
,但其方法是拷贝而非移动!原因在于,text
被声明为一个 const std::string
,经 move
变成右值后,const
性质并不会消失。const
的右值引用参数无法用于移动构造函数,编译器只能退而求其次选择拷贝构造。
- 从这个例子中我们应该学到两件事:(1)不要将想要移动的对象声明为
const
,否则后续的移动操作会静默地变成拷贝操作(即使用了std::move
转型也是无效的)。(2)std::move
不仅不移动对象,甚至不保证它转型的对象是能够被移动的。你只能确定经move
后的对象是一个右值。
std::forward
与std::move
类似,但它是一个带条件的类型转换。为了理解这个条件,先来看一个常见的使用场景:在模板函数中接受一个万能引用参数并将其传递给另一个函数。
void process(const Widget& lvalArg); // process lvalues
void process(Widget&& rvalArg); // process rvalues
template<typename T> // template that passes
void logAndProcess(T&& param) // param to process
{
auto now = // get current time
std::chrono::system_clock::now();
makeLogEntry("Calling 'process'", now);
process(std::forward<T>(param));
}
Widget w;
logAndProcess(w); // call with lvalue
logAndProcess(std::move(w)); // call with rvalue
在两次调用中,我们希望前者在 logAndProcess
中调用 process
的左值版本,而后者调用右值重载版本。param
是一个既能绑定到左值又能绑定到右值参数的万能引用,然而,param
自身是左值,直接将其传给 process
将永远调用左值版本。
我们希望有一种机制,当 param
是用左值的入参初始化时,调用左值版本;用右值的入参初始化时,将其转型为右值并调用右值版本。这正是 std::forward
所做的:只有当其参数是由右值初始化时,才将其转型为一个右值。其实现的诀窍在它的模板类型 T
中,详细原理见Item 28讲解。
- 功能上说,
std::move
和std::forward
是可以互相替代的;说到底,它们只是一个类型转换,我们完全可以随便自己写。但还是有必要区分它们的使用:语法上,后者需要额外传递一个模板类型参数;语义上,二者代表了非常不同的动作:前者是执行一个向右值的转换,而后者只是将一个对象原样地传递(forward)给另一个函数,保留其左右值性。
总结
std::move
进行向右值的无条件类型转换。其自身不会移动任何东西。(真正的移动是在使用 move 后的右值的函数中发生的)std::forward
只有当其参数是绑定到右值上时才将其类型转换为右值。(通常用于万能引用)std::move
和std::forward
在运行时都不会做任何事。