左值 lvalue 是有标识符、可以取地址的表达式。字符串字面量是左值,在C++中 字符串其实是const char[N],其实是个常量表达式,在内存中有明确的地址。
纯右值 prvalue 是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。
需要注意的是类型是右值引用的变量是一个左值。毕竟对于一个右值引用的变量,你是可以取地址的,这点上它和左值完全一致。
std::move(ptr)
就有趣点了。它的作用是把一个左值引用强制转换成一个右值引用,而并不改变其内容,结果是指向 ptr
的一个右值引用。我们可以把 std::move(ptr)
看作是一个有名字的rvalue。为了跟无名的纯右值 prvalue 相区别,C++ 里目前就把这种表达式叫做 xvalue。跟左值 lvalue 不同,xvalue 仍然是不能取地址的——这点上,xvalue 和 prvalue 相同。所以,xvalue 和 prvalue 都被归为右值 rvalue。如果一个 prvalue 被绑定到一个引用上,它的生命周期则会延长到跟这个引用变量一样长。
移动语义
使得在 C++ 里返回大对象(如容器)的函数和运算符成为现实,因而可以提高代码的简洁性和可读性,提高程序员的生产率。
对于 template <typename T> foo(T&&) 这样的代码,如果传递过去的参数是左值,T 的推导结果是左值引用;如果传递过去的参数是右值,T 的推导结果是参数的类型本身。
如果 T 是左值引用,那 T&& 的结果仍然是左值引用——即 type& && 坍缩成了 type&。
如果 T 是一个实际类型,那 T&& 的结果自然就是一个右值引用。
如何实现移动?
要让你设计的对象支持移动的话,通常需要下面几步:
- 你的对象应该有分开的拷贝构造和移动构造函数(除非你只打算支持移动,不支持拷贝——如
unique_ptr
)。 - 你的对象应该有
swap
成员函数,支持和另外一个对象快速交换成员。 - 在你的对象的名空间下,应当有一个全局的
swap
函数,调用成员函数swap
来实现交换。支持这种用法会方便别人(包括你自己在将来)在其他对象里包含你的对象,并快速实现它们的swap
函数。 - 实现通用的
operator=
。 - 上面各个函数如果不抛异常的话,应当标为
noexcept
。这对移动构造函数尤为重要