Skip to content

左值右值

左值右值,经常听的东西,于是稍微记录 —— (实际上看了 Efficient C++ 之后,结合一些新标准,觉得还是有必要写写)

所有权,资源转移,移动语义等蹲一下新坑,现在先聚焦一下左右值以及左右值引用。

左值与右值

左值

先说左值,表示了一个占据内存中某个可识别的位置(也就是一个地址)的对象的表达式

比如说 int x = 10;,其中的 x 就是一个非常标准且简单的左值 —— 有名字有内存位置有地址

右值

和左值相对的,它没有地址,比如临时对象等

std::string name = std::string("Alice"); 此处的std::string("Alice"); 就是一个经典的右值,一个临时对象

再比如x + 1, std::move(name)

当然,从C++11开始引入了纯右值和将亡值的概念,

纯右值 prvalue

C++
10
std::string("Alice")
x + 1

这些通常是纯右值。

它们更像是“临时计算出来的值”。

将亡值 xvalue

语义上表示一个生命将尽的资源,常用move进行构造

C++
std::move(name)

它不是创建了一个新的临时对象,而是把已有对象 name 转换成一个“可以被移动”的表达式。

直觉

左值一般来说是长期存在,可以用于资源拷贝; 右值一般来说是临时对象,即将被销毁,不过可以被“移动”

引用

又扯到了引用,常见的 ——

C++
T& value;
const T& value;
T&& value;

左值引用

T& value,实际上是“左值的引用”,或者我们常说的取别名,此处它只能被左值赋值,比如 ——

C++
int a = 10;
int& x = a;      // 合法
int& x = 10;     // 非法

所以同样的,在函数参数中 ——

C++
void Foo(int& a);

Foo(x);           // 合法
Foo(10);          // 非法

此时的语义为"我借用了这个变量,并且会修改它"

不过有一个宽松的“例外”——(Unlimited Rule!少数例外原则

就是使用 const 修饰的左值引用,因为加上 const 之后语义变成了—— “只读这个东西,但是不修改”。

C++
const int&x = 10;  // 合法

Foo(const int& a);
Foo(10);           // 合法

实际上它可以类似理解于

C++
const T temp = rvalue;
const T& value = temp;

假设对于局部引用const std::string& ref = std::string("Alice");

那么临时对象Alice的生命周期就会被延长和ref一致

右值引用

T&& value

const T& value 可以传入右值,但是问题也显而易见,就是传入的只读资源是不可消费的,于是提出了右值引用。

C++
void Take(std::string&& s) { std::cout << s << "\n"; }

Take(std::string("Alice"));    // 合法
Take(name);                    // 非法
Take(std::move(name));         // 合法

move

std::move() 表示把一个左值转化成右值表达式,比如std::move(name),就是把原先的name左值转化成一个右值表达式。

不过值得注意的,std::move只会做表达式的转化,而不会真的移动资源

比如

C++
std::string name = "Alice";
std::string other = std::move(name);

此处std::move只是把name做了转化,而真正把右值表达式进行消费的是other的移动构造函数

值得注意的 —— 此时name依旧是一个可以使用的对象,但是不要假定它的值还是Alice,也就是之后的代码不要依赖name == Alice这个逻辑来进行。不过可以重新赋值等继续使用。

右值引用变量本身是左值

说起来非常的拗口,实际上就是 —— 哪怕声明的是右值引用变量,但是在使用的时候会被当做左值使用。

还是很绕,例子 ——

C++
void Take(std::string&& s) { std::string other = s; }

此处other的实例化会触发拷贝构造,因为 s 是左值表达式,尽管它在传入的时候被声明成了右值引用变量

所以如果想要使用移动构造,则需要写

C++
void Take(std::string&& s) { std::string other = std::move(s); }

Released under the MIT License.