左值 右值 一

https://en.cppreference.com/w/cpp/language/value_category

值的类别

任何C++的表达式(带有变量名、操作数等的操作符)都可以特征化为两个相互独立的属性:类型和值类别。每一个表达式都有非引用类型,每一个表达式都严格属于三个几本值类别中的一个:prvalue(纯右值) xvalue(亡值)和lvalue(左值)

  • glvalue(“generalized” lvalue)(泛左值)是一个表达式,它的求值决定了一个对象,位运算或者函数;

  • prvalue(“pure” rvalue)(纯右值)是一个表达式,表达式有下面两种:

    • 计算一个操作符的运算对象,或者一个void表达式(这个右值没有结果对象)
    • 初始化一个对象或者位字段(这个纯右值被称作有一个结果对象)。除了decltype,所有的类和数组都有纯右值结果对象,就算被丢弃也是有的。这个结果对象可以是一个变量,一个用new表达式创建的对象,一个通过临时实例化创建的临时对象,或者是他们中的一个成员。
  • xvalue(an “eXpiring” value)(到期的值,亡值)是一个泛左值,表明一个结构体或者位字段的资源可以被重新利用

  • lvalue(历史遗留称谓,表示可以出现在赋值表达式左边)(左值)是一个glvalue(泛左值),不是xvalue(亡值)

  • rvalue(历史遗留称谓,表示可以出现在赋值表达式右边)(右值)是一个prvalue(纯右值)或者xvalue(亡值)

注意: 这个分类与C++以前的标准版本区别比较大,详情参考下面信息

基本分类

lvalue 左值

如下表达式是左值表达式:

  • 变量名、函数名、模板形参的名字或者数据成员的名字,不论其类型,比如std::cin std::endl。即使变量的类型是右值的引用,由其名字组成的表达式仍然是左值表达式

  • 返回类型是左值引用的函数调用或者操作符重载,比如std::getline(std::cin, str) std::cout << 1 str1 = str2 ++it

  • a = b a += b a %= b,所有内置的赋值和混合赋值表达式

  • ++a --a,内置的前置自增和前置自减表达式

  • *p,内置的间接取值表达式

  • a[n] p[n],内置的下标索引表达式,当a[n]的一个操作对象是一个数组的lvalue(左值)时(since C++11)

  • a.m,一个对象成员的表达式,除了m是一个枚举类型的成员或者是一个非静态成员函数,或者a是一个右值,但是m是一个对象的非静态数据成员

  • p->m,内置的指针成员表达式,除了m是一个枚举成员或者非静态函数

  • a.*mp,对象的成员指针,a是一个lvalue(左值),mp是一个数据成员指针

  • p->*mp,内置的指向成员指针的指针表达式,mp是一个数据成员指针

  • a, b,内置的逗号表达式,b是一个lvalue(左值)

  • a ? b : c,对于某些b和c的三元条件表达式(比如,当它们都是lvalues(左值))

  • 一个字符串,比如Hello, world!

  • 转换成lvalue(左值)引用类型的转换表达式,比如static_cast<int&>(x)

  • 返回类型是一个函数左值引用的函数调用或者操作符重载表达式(since C++11)

  • 转换为函数rvalue(右值)引用的表达式,比如static_cast<void (&&)(int)>(x)(since C++11)

特性

  • 与glvalue(泛左值)相同
  • 可以用内置的取址表达式取址:&++i &std::endl都是合法的
  • 可修改的lvalue(左值)可以用作内置赋值或者混合赋值表达式的左边
  • lvalue(左值)可以初始化lvalue(左值)引用;创建一个新的名字用来关联表达式定义的对象

prvalue 纯右值

下面的表达式是纯右值

  • 字面意思的数据(除了字符串之外,比如Hello),比如42 true nullptr

  • 返回值不是引用的函数调用或者重载操作符,比如str.substr(1, 2) str1 + str2 it++

  • a++ a--,内置的后置自增和后置自减表达式

  • a + b a % b a & b a << b以及所有内置的算术表达式

  • a && b a || b !a,所有内置的逻辑表达式

  • a < b a == b a >= b,所有内置的比较表达式

  • &a,内置的取址表达式

  • a.m,对象成员表达式,m是一个枚举成员或者非静态成员函数,或者a是一个rvalue(右值)并且m是一个非引用类型的非静态数据成员(until C++11)

  • p->m,内置的指针成员表达思,m是一个枚举成员或者非静态成员函数

  • a.*mp,对象成员指针表达式,mp是一个成员函数,或者a是一个rvalue(右值)并且mp是一个数据成员指针(until C++11)

  • p->*mp内置的指针成员的指针表达式,mp是指向成员函数的指针

  • a, b,内置逗号表达式,b是rvalue(右值)

  • a ? b : c,对于某些b和c的三元判断表达式

  • 非引用类型的转换,比如static_cast<double>(x) std::string{} (int)42`

  • this指针

  • 枚举项

  • 非类型模板参数,除非类型是类或者(since C++20)lvalue(左值)引用类型

  • lambda表达式,比如[](int x){ return x * x; }(since C++11)

  • requires表达式,比如requires (T i) { typename T::type; }(since C++20)

  • 特殊化的概念,比如std::equality_comparable<int>(since C++20)

特性

  • 与rvalue(右值)一样

  • prvalue(纯右值)没有多态:它所标识的对象的动态类型,取决于表达式的类型

  • 非类非数组的prvalue(纯右值)不能被cv(const volatile)限定。(注意:一个函数调用或者转换表达式可以产生非类的prvalue(纯右值)的cv限定类型,但是cv限定很快就会被去除)

  • prvalue(纯右值)不能有非完整类型(除了void,或者在decltype中使用)

  • prvalue(纯右值)不能有抽象类类型或者数组类型

xvalue 亡值

下面的表达式是xvalue(亡值)表达式:

  • 返回类型是rvalue(右值)引用的函数调用或者重载的操作符,比如std::move(x)

  • a[n],内置的下标索引表达式,其操作对象是数组的rvalue(右值)

  • a.m,对象成员表达式,a是rvalue(右值),m是一个非索引类型的非静态的数据成员

  • a.*mp,对象的一个成员指针表达式,a是rvalue(右值)并且mp是数据成员指针

  • a ? b : c,对于某些b和c的三元条件表达式

  • 对于对象类型rvalue(右值)索引转换,比如static_cast<char&&>(x)

  • 任何确定临时对象的表达式,在临时实例化完成后(since C++17)

特性

  • 与rvalue(右值)一样
  • 与glvalue(泛左值)一样

与所有的rvalues(右值)一样,xvalues(亡值)可以绑定到rvalue(右值)引用;与所有的glvalues(泛左值)一样,xvalues(亡值)可以有多态性,非类的xvalues(亡值)可以有cv限定。

混合分类

glvalue 泛左值

一个glvalue(泛左值)表达式可以是lvalue(左值)或者xvalue(亡值)

特性

  • glvalue(泛左值)可以隐式的转化为prvalue(纯右值),比如lvalue(左值)到rvalue(右值),数组到指针,函数到指针的隐式转换

  • glvalue(泛左值)可以是多态的:对象的东太太类型不必是表达式的静态类型

  • glvalue(泛左值)可以有未完成的类型,只要表达式允许

rvalue 右值

一个rvalue(右值)表达式可以是prvalue(纯右值)也可以是xvalue(亡值)

特性

  • rvalue(右值)的地址不能通过内置的取地址符进行操作:&int() &i++ &42 &std::move(x)都是非法的

  • rvalue(右值)不能放到内置的赋值操作符或者混合赋值操作符的左侧

  • rvalue(右值)可以用在初始化const类型的lvalue(左值)索引,这种情况下,这个rvalue(右值)对象的生命周期被延长为索引的作用域结尾

  • rvalue(右值)可以用作初始化rvalue(右值)索引,这种情况下,这个rvalue(右值)对象的生命周期被延长到索引的作用域结尾(since C++11)

  • 当用做函数实参,并且有两个重载的函数可用,一个是传递rvalue(右值)引用,另一个是传递const lvalue(左值)引用,rvalue(右值)将会绑定到rvalue(左值)引用的重载上(因此,当拷贝和移动构造函数都可用时,rvalue(右值)的参数调用移动构造函数,拷贝和移动赋值操作与此类似)(since C++11)

特殊分类

为决定成员的函数调用

a.mf p->mf,当mf是非静态成员函数,并且表达式a.*pmf p->*pmf,pmf是成员函数指针,被认为是prvalue(纯右值)表达式,但是它们不能用作初始化引用,不能用作函数的参数。除了用作函数调用操作符左侧参数(比如(p->*pmf)(args)),不能用作其他任何目的

void

返回void的函数调用表达式,转换为void的表达式,抛出异常的表达式都被定义为prvalue(纯右值),但是它们不能用作初始化引用,或者函数参数。可以用在舍弃值的语境下(比如,在一行,作为逗号操作符的左侧操作数)和函数返回void的返回语句中。另外,抛出异常的语句可以应做三元运算符?:的第二个和第三个操作数

void表达式没有结果对象(since C++17)

位字段

代表位字段的表达式(比如,a.m,a是一个结构体struct A { int m: 3; }的lvalue(左值))是一个glvalue(泛左值)表达式:他可以用作赋值操作符的左侧,但是它的地址不能获得,并且不能绑定非const lvalue(左值)引用。const lvalue(左值)引用或者rvalue(右值)引用可以通过位字段glvalue(泛左值)初始化,但是会有一个对于位字段的临时拷贝,不能直接绑定位字段

历史

CPL

CPL是第一个为表达式引入值类别的:所有的CPL表达式都能以“右侧模式”求值,但是只有一些表达式在“左侧模式”有意义。当在右侧模式求值时,表达式被当作求值的规则(右侧的值,或者右值)。当在左侧求值时,表达式被给定一个明确的地址(左侧的值或者左值)。左和右在这里表示赋值左侧和赋值右侧

C

C语言沿用了相似的分类,除了赋值角色不再那么重要:C语言的表达式被归类为左值表达式和其他(函数和非对象值),左值表示定义一个对象的表达式,一个定位器的值

C++98

在2011之前的C++沿用了C的模式。但是恢复了非左值表达式名字为右值,令函数为左值,增加了可以绑定引用到左值上,但是只有const引用可以绑定到右值上。一些非左值C语言表达式在C++中成为了左值表达式

C++11

随着移动语义添加到C++11,值的分类被重新定义,用来区分表达式的两种独立属性

  • 拥有身份:用来确定表达式是否与另一表达式有同一实体,比如通过比较对象或者函数的地址(直接或间接的)

  • 可被移动:移动构造函数,移动赋值操作或者另一个重载的函数实现了移动语义,可以绑定到这个表达式

在 C++11中:

  • 拥有身份且不可被移动的表达式被称作左值 (lvalue)表达式;
  • 拥有身份且可被移动的表达式被称作亡值 (xvalue)表达式;
  • 不拥有身份且可被移动的表达式被称作纯右值 (prvalue)表达式;
  • 不拥有身份且不可被移动的表达式无法使用

拥有身份的表达式被称作“泛左值 (glvalue) 表达式”。左值和亡值都是泛左值表达式。

可被移动的表达式被称作“右值 (rvalue) 表达式”。纯右值和亡值都是右值表达式。

C++17

C++17 中,某些场合强制要求进行复制消除,而这要求将纯右值表达式从被它们所初始化的临时对象中分离出来,这就是我们现有的系统。要注意,与 C++11 的方案相比,纯右值已不再是可被移动。

版权声明:本文版权归作者所有,如需转载,请标明出处

原文地址:https://www.cnblogs.com/studywithallofyou/p/14569859.html