Skip to main content

Command Palette

Search for a command to run...

C++中的值类别

谈谈lvaue, xvalue, prvalue, glvalue,rvalue

Published
C++中的值类别

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++的优化机制下,表达式只存在于编译器的脑海(代码段)和寄存器上, 而没有一个确切的内存地址。

image.png

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.

区分和分类

因此表达式可以分为四种类型,

  1. 有身份,不可移动
  2. 有身份,可移动
  3. 没有身份,可移动
  4. 没有身份,不可移动

对于第四种是没有意义的,因为没有身份,必然可移动。

  • 由于标准库中,rvalue这一词是指可移动,所以,后2和3加在一起应该叫rvalue。
  • 左值和右值没有交集,并且要覆盖所有,所以,1应该叫lvalue。
  • 1和2加在一起,都有身份,接近于左值,叫做generalized lvalue,简称glvalue。
  • 3没有身份,比如字面量,临时变量,是一类rvalue,并且是非常纯粹的右值,叫做pure rvalue,简称prvalue。
  • 好了,就剩2了,它有身份,又可以被move,叫做将亡值(eXpiring value),简称xvalue。

Ref: C++11中的值的类型

总结一些,如下图

image.png

  • 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

性质:没有身份,可移动

描述:

  1. 除字符串外的纯字面值
  2. 求值结果相当于字面值或者一个不具名的临时对象

举例: 除字符串字面值以外的字面值、返回非引用类型的函数调用、 后置 自增/自减运算符连接的表达式i++/i-- 、 算术表达式(a+b、a&b、a<=b、a<b)、取地址表达式(&a)等。

自增/自减运算符

对于前置运算符(++i, --i),是先对 i 进行一次自增/自减操作后返回 i ;对于后置运算符(i++, i--) 是对 i 生成一份拷贝,对拷贝进行自增/自减操作后,返回临时生成的拷贝,再对 i 进行自增/自减操作的。

xvalue

性质: 有身份,可移动

描述:

  1. 返回右值引用的函数的调用表达式
    std::move(x);
    func(); //func的声明:Foo&& func();
    
  2. 转换为右值引用的转换函数的调用表达式
    static_cast<Foo&&>(x);
    
  3. xvalue的成员
    std::move(x).m_data;
    func().m_data;  //func的声明:Foo&& func();
    static_cast<Foo&&>(x).m_data;
    
    Ref: 话说C++中的左值、纯右值、将亡值

More from this blog

<Programming with Types>随想: Chapter 2. Basic types

类型限制了一个变量可以接受的有效值的集合,对数据可以进行的操作,数据的意义。 空类型(The empty type) 根据类型的定义,类型定义了可以接受的有效值集合,那么这个集合有没有可能为空?答案是有可能的,TypeScirpt 的 never 就是这种类型。 需要注意的是,空类型不同与 void, 后者是有效值集合当中只有一个值,但这个值没有任何意义。而空类型的有效值集合本身是空的。 使用场景 控制流分析 在函数调用时,标志一个函数不会返回任何值: 在调用过程中抛出异常、死循环或者程序崩溃...

Mar 5, 2023
<Programming with Types>随想: Chapter 2. Basic types

精读《设计机器学习系统》-ch04: 训练数据

不同于 Chapter03 从系统的角度来处理数据,这一章从数据科学的视角来处理数据。这章的标题是“training Data”,而非“training dataset”,因为 数据集(dataset) 意味着有限(finite)和固定(stationary), 而现实生产环境中的 数据(data) 通常是 无限 并且 不固定 的。 抽样 抽样方法在 ML 项目的生命周期中无处不在,在这一节中,我们使用生成训练数据作为例子。 那为什么需要抽样?直接使用全部数据不可以吗? 首先,在现实世界中,并不...

Dec 10, 2022
精读《设计机器学习系统》-ch04: 训练数据
7

70 Talk

19 posts