C++中所有的值都可以分为左值右值

左值

左值是“有身份“的值。一个左值一定是能够被取地址的。

1
2
int x = 5;      // x 是左值
int& ref = x; // ref 是左值引用,绑定到左值 x

左值通常是程序中可持久化存在,能被频繁访问的对象。左值一定会被分配一个可操作的地址。

左值的生命周期通常较长,一般超出单个表达式的作用域。

右值

右值有两个核心特征: 可移动性 和 临时性。

C++11 之后,右值也可以分为两类。

  1. 纯右值
  2. 将亡值

纯右值

纯右值是纯粹的临时值,没有身份,不可以取地址。

通常,纯右值用来表示计算结果,非引用返回值,构造函数调用生成的临时对象。

1
thread(func); // 这样的一个值是纯右值。

一个纯右值如果没有一个左值来接收,那么这个纯右值将会立即死去。

将亡值

将亡值是一个具有身份但即将被销毁的值。同样地,一个将亡值如果没有一个左值来接收,这个将亡值也会立即死去。

将亡值通常是被move等标记过的值,或者右值引用返回值。

通过将亡值,C++支持了移动语义

利用移动语义,我们可以将资源从一个实例转移到另一个实例。在某些情况下,这十分关键。

例如,智能指针unique_ptr具有独占所有权的机制。所以在unique_ptr间转移资源时,移动语义非常有用。

1
2
3
4
// 将一个unique_ptr的资源转移到另一个unique_ptr

unique_ptr<string>ptr1 = make_unique<string>(str);
unique_ptr<string>ptr2 = move(ptr1);

其中move()的作用是让左值ptr1变成一个将亡值。

左值引用、右值引用与移动语义

C++11后,编译器支持使用&&来表示一个右值引用。

1
2
3
int a = 10;         // 左值
int & ref = a; // 左值引用
int && ref2 = a; // 右值引用

move可以将一个左值转化为一个右值引用,以便触发移动构造函数,从而使用移动语义。

拷贝构造函数类似,移动构造函数也是通过不同的参数重载的构造函数。

1
2
3
4
5
6
String(String&& other)
: data_(other.data_), size_(other.size_) { // 1. 窃取资源
// 2. 置空源对象,避免重复释放
other.data_ = nullptr;
other.size_ = 0;
}

在移动构造函数中,应该将管理资源的原裸指针置空。对于STL中的容器或者std::unique_ptr,应该使用move进行资源的移动。

而对于其他内置数据类型,则不必过多关心。