C++中的值类别
谈谈lvaue, xvalue, prvalue, glvalue,rvalue

Before C++ 11
在C++11之前,表达式只分为左值(lvalue)和右值(rvalue)两种类别, 左值和右值
- 一个表达式,如果能对它进行&操作取地址, 则是左值(lvalue)。
- 否则,它是右值(rvalue)。
这样的话,每一个值不是左值,就是右值,判断规则单一,并且覆盖左右值。
C++11 and after
在C++11及之后,值的类型共分为五种, 分别是lvaue(左值), xvalue(将亡值), prvalue(纯右值), glvalue(泛左值),rvalue(右值)
两条独立属性
其实对于这些值有两条独立的属性:
身份(identity):
在cppreference和Bjarne Stroustrup的笔记中,是这样定义的:
如果一个值有身份,可以通过比较对象或者函数的地址,确定两个表达式是否是同一个。
has identity: it's possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or functions they identify (obtained directly or indirectly);
ref: value_category
其实, 对于将亡值(xvalue)其实也存在身份,但是你无法对于一个将亡值取它的地址.
int&& f1() {
return 1;
}
int main() {
std::cout << &f1() << endl; // Error: Cannot take the address of an rvalue of type 'int'
return 0;
}
也对,你无法对一个右值进行取地址操作。
因此可以从另一个角度理解 identity 这条性质: identity 取决于一个对象是否真实存在于进程的内存中。对于不存在indentiy性质prvalue来说,在C++的优化机制下,表达式只存在于编译器的脑海(代码段)和寄存器上, 而没有一个确切的内存地址。

identity and movability. The first deals with the the "solidness" with which something exists. That's important because the C++ abstract machine is allowed to and encouraged to aggressively change and shrink your code through optimizations, and that means that things without identity might only ever exist in the mind of the compiler or in a register for a brief moment before getting trampled on
Ref: stackoverflow
反过来再理解第一句话,对于xvalue, 其实并非要取一个他的地址,只需要这个xvalue存在于进程的内存地址空间中,并不一定需要程序员能对它进行取地址& 操作。
可移动(can be moved from)
表达式是否拥有移动语义,能否调用移动构造函数和移动复制函数,或者其他实现了移动语义的函数。
can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.
区分和分类
因此表达式可以分为四种类型,
- 有身份,不可移动
- 有身份,可移动
- 没有身份,可移动
- 没有身份,不可移动
对于第四种是没有意义的,因为没有身份,必然可移动。
- 由于标准库中,rvalue这一词是指可移动,所以,后2和3加在一起应该叫rvalue。
- 左值和右值没有交集,并且要覆盖所有,所以,1应该叫lvalue。
- 1和2加在一起,都有身份,接近于左值,叫做generalized lvalue,简称glvalue。
- 3没有身份,比如字面量,临时变量,是一类rvalue,并且是非常纯粹的右值,叫做pure rvalue,简称prvalue。
- 好了,就剩2了,它有身份,又可以被move,叫做将亡值(eXpiring value),简称xvalue。
Ref: C++11中的值的类型
总结一些,如下图

- C++11前的
lvalue== C++11及之后的lvalue - C++11前的
rvalue== C++11及之后的prvalue
值的详细说明和举例
对于C++中值类别,只会是lvalue, xvalue, prvalue中的一种。
lvalue
性质: 有身份,不可移动
描述: 能够用&取地址的表达式是左值表达式。
举例:函数名和变量名(实际上是函数指针和具名变量,具名变量如std::cin、std::endl等)、返回左值引用的函数调用、前置自增/自减运算符连接的表达式++i/--i、由赋值运算符或复合赋值运算符连接的表达式(a=b、a+=b、a%=b)、解引用表达式p、*字符串字面值"abc"等。
字符串字面量
不是所有的字面值都是纯右值,字符串字面值是唯一例外。 早期C++将字符串字面值实现为char数组,为每个字符都分配了内存空间,供程序员操作。下面代码可以通过编译。
int main() {
cout << &"abc" << endl;
}
prvalue
性质:没有身份,可移动
描述:
- 除字符串外的纯字面值
- 求值结果相当于字面值或者一个不具名的临时对象
举例: 除字符串字面值以外的字面值、返回非引用类型的函数调用、 后置 自增/自减运算符连接的表达式i++/i-- 、 算术表达式(a+b、a&b、a<=b、a<b)、取地址表达式(&a)等。
自增/自减运算符
对于前置运算符(++i, --i),是先对 i 进行一次自增/自减操作后返回 i ;对于后置运算符(i++, i--) 是对 i 生成一份拷贝,对拷贝进行自增/自减操作后,返回临时生成的拷贝,再对 i 进行自增/自减操作的。
xvalue
性质: 有身份,可移动
描述:
- 返回右值引用的函数的调用表达式
std::move(x); func(); //func的声明:Foo&& func(); - 转换为右值引用的转换函数的调用表达式
static_cast<Foo&&>(x); - xvalue的成员
Ref: 话说C++中的左值、纯右值、将亡值std::move(x).m_data; func().m_data; //func的声明:Foo&& func(); static_cast<Foo&&>(x).m_data;




