C++值类别 Value-categories

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.
每个表达式(expression)都具有两个独立的属性: typevalue category.

Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories: prvalue, xvalue, and lvalue.
每个表达式都有具体类型并且这个表达式一定是prvalue expressionxvalue expressionlvalue expression中的其中一种类别.

备注:
声明变量不是一个表达式, 因此声明出来的变量, 只有类型, 没有值类别.
值类别指的是一个表达式, 即这个值在右边(RHS--Right Hand Side)充当分配(assignment)、比较(compare)、递增、递减等操作.
因此要再三强调,typevalue category 是两个独立的属性. 一个值只有类型, 而一个表达式有类型和值类别.

 

RHS 和 LHS

RHS: Right Hand Side , LHS: Left Hand Side, 这两个术语分别代表一个声明语句/表达式的两边, 例如:
int a = 10; , LHS 表示 int a, RHS 表示 10;

备注:
所有 value-category 都指的是 RHS

# LHS: a 的类型是 int, 仅此而已, a 在这里没有 value-category 这个属性.
# RHS: 10 的类型是 int, 10同时也是一个表达式, 10的value-category是 prvalue expression.
# 这个语句其实是将: `int a;` 和 `a = 10` 合并成了一个步骤, 所以仔细思考一下就能明白.
int a = 10;

即便是函数调用也是 RHS, 只不过表述的比较隐晦.

# 在编译期就已经为 foo 这个 scope 分配一个 int x 地址.
void foo(int x) {};

# 这里传递 a 给 foo 函数, 其实是充当 x = a 的一个 赋值/分配 (assignment)的作用.
# 即: x = a; a在右边, a是一个表达式, 这时a是一个可以通过 & 获取地址的变量名(identity), 因此 a 是一个 Lvalue-Expression.
foo(a);

 

类型(Type)

Non-Reference T: intchardoublefloatlong
Lvalue-Reference T&:int&char&double&float&long&
Rvalue-Reference T&&: int&&char&&double&&float&&long&&

备注:
类型是一个独立的属性, 它跟类别不是从属关系, 即: Lvalue-Reference 不等同于 Lvalue-Expression.

 

类别(Value-category)

所有 value-category 都指的是 RHS, 即便是函数调用也是 RHS, 只不过表述的比较隐晦, 重要的事情多说一遍.

lvalue expression

have identity and cannot be moved from are called lvalue expressions;
有变量名并且不能作为参数传递给func(T&&)的值被称为左值表达式.
即: 可以使用 & 访问到内存地址的对象, 当它在RHS时, 它就是一个左值表达式.

int a = 10;            
int b = 5;                
b = a;                    # 变量 a 在这里是一个表达式, 可以使用 & 获取它的内存地址, 所以它是一个左值表达式.

int * aptr = &a;          
cout << &aptr;            # 指针变量 aptr 在这里是一个表达式, 可以使用 & 获取它的内存地址, 
                          # 它的内存地址保存的是变量a的地址, 所以它是一个左值.
  1. the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
# 虽然 c 的类型是 int && (rvalue reference), 但是仍然是一个左值表达式.
# 原因是它可以使用 & 来获取内存地址, 因此它是一个左值表达式.
int && c = 20;
b = c;                    
  1. a function call or an overloaded operator expression, whose return type is lvalue reference, such as std::getline(std::cin, str), std::cout << 1, str1 = str2, or ++it;
    调用常规函数或重载函数,当返回值是 lvalue reference: T& 时, 这个值是一个 lvalue expression.
#include <iostream>
using std::cout;
using std::endl;

int & foo(int & x) {
    x += 1;
    return x;
}

int main(void) {
    int a = 10;

    # a 在表达式 int& x = a 右边, a可以通过 & 获取到内存地址, a是 lvalue-expression.
    # foo(a) 在表达式 int & b = foo(a); 的右边, foo函数的返回值的
    # 类型是 lvalue-reference, 所以这个返回值是一个 lvalue-expression.
    # 声明语句 int & b, 其中 b 在这里只是一个 lvalue-reference, 它没有value-category.
    int & b = foo(a);

    # a 在这里是作为重载函数(cout)的参数, 所以a是一个RHS, 
    # a是一个表达式, 所以 a 现在是一个 lvalue-reference 也是一个 lvalue-expression.
    cout << a << "; " << &a << endl;
    cout << b << "; " << &b << endl;
    // output:
    // 11; 0000006F2DB6F8F4
    // 11; 0000006F2DB6F8F4
}
  1. a = b, a += b, a %= b, and all other built-in assignment and compound assignment expressions;
    这里表示, 内置赋值操作都是调用模板重载函数并且返回的值是 lvalue-reference即: T&, 因此这个表达式是一个 lvalue-expression
  2. ++a and --a, the built-in pre-increment and pre-decrement expressions;
    这里表示, 前置递增和前置递减重载函数返回的值是 lvalue-reference即: T&, 因此这个表达式是一个 lvalue-expression.
  3. *p, the built-in indirection expression;
    通过解引用获取到指针指向的对象, 该对象既然能被指针指向,表示它本身就可以使用&, 所以这个值是一个lvalue expression.
    ....

 

xvalue

have identity and can be moved from are called xvalue expressions;
有变量名并且可以作为参数传递给func(T&&)的值被称为xvalue expression.

注意:
xvalue大部分情况下都是没有变量名的, 这种情况xvalue都隶属于rvalue.
然而还有小部分情况下 xvalue 是有变量名的, 但是这个变量名是不能通
& 来获取内存地址, 这种情况 xvalue 隶属于 glvalue.
只有 lvalue 可以通过 & 获取内存地址.

  1. a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);
    函数、重载函数或表达式,只要返回值的类型是rvalue reference: T&& , 都被称为 xvalue expression, 最直观,使用最多的是 std::move(x).
#include <iostream>
using std::cout;
using std::endl;
using std::move;

int && foo() {
    return 10;
};

void bar(int && x) {
    cout << "bar called" << endl;
};

int main(void) {

    # 最简单的方式就是用 move, 因为 move 返回值类型是 T&&.
    int a = 10;
    bar(move(a));                    
    
    # foo() 返回 T&&, 是一个 xvalue expression.
    bar(foo());

    # error C2102:  '&' requires l-value
    # 只有 lvalue expression 能使用 & 获取内存地址, 所以 foo() 表达式返回值的值类别一定是 xvalue expression.
    // cout << &foo() << endl;                

    return 0;
}
  1. a[n], the built-in subscript expression, where one operand is an array rvalue;
    使用 using 来为数组类型做别名, 通过花括号一致性初始化, 可以声明一个array rvalue, 这是一个 prvalue expression
    暂时想不到使用场景,这一项定义有点虚, 而且没办法证明它是一个xvalue expression.
#include <iostream>
using std::cout;
using std::endl;

void foo(int (&&x)[2][3]) {
    cout << "foo called" << endl;
}

int main(void) {
    using array_prvalue = int[2][3];
    array_prvalue{};                                // this expression is a prvalue expression.
    foo(array_prvalue{});                         // binds to prvalue expression.
}
  1. a.m, the member of object expression, where a is an rvalue and m is a non-static data member of non-reference type;
    a是一个prvalue expression的对象, 并且它的成员对象m不是一个引用, 那么就是 xvalue expression.
#include <iostream>
using std::cout;
using std::endl;

struct X {
    int i;
};

void foo(int && x) {
    cout << "foo called" << endl;
};


int main(void) {

    foo(X().i);              # X().i 是一个 xvalue expression.

    # 等同于
    X x{};
    foo(move(x.i));          # x.i 是一个 lvalue expression, move(x.i) 是一个 xvalue expression.

    return 0;
}

 

prvalue

do not have identity and can be moved from are called prvalue ("pure rvalue") expressions
没有变量名并且可以作为参数传递给func(T&&)的值被称为prvalue expression.

备注:
换另外一种理解我认为更好:
那些返回类型是非引用(T)的函数、重载函数或表达式, 被称为 prvalue expression.

1.a literal (except for string literal), such as 42, true or nullptr;
常量类型数据, 例如: 42, true, nullptr , 都是 prvalue expression.

# 10 在右边(RHS), 它有两个属性: 
# 1. 是非引用类型(non-reference). 
# 2. 是 prvalue expression, 因为无法获取它的内存地址.
# 最重要的是, T 既可以绑定到 lvalue 上, 也可以绑定到 xvalue 上.
int a = 10;        
  1. a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++;
    函数、重载函数和表达式,只要返回值是非引用, 例如: str.substr(1, 2)str1 + str2iterator++, 都是 prvalue expression.
#include <iostream>
using std::cout;
using std::endl;

void foo(int && x) {
    cout << "foo called" << endl;
};

void bar(int) {
    cout << "bar called" << endl;
}

int main(void) {

    int a = 10;
    int b = 20;
    (a + b);            # prvalue
    foo((a + b));       # prvalue can bind to xvalue.
    bar((a + b));       # prvalue can bind to lvalue.

    return 0;
}
  1. a++ and a--, the built-in post-increment and post-decrement expressions;
    这里表示, 后置追加的重载函数原型返回的类型是 T, 即非引用类型, 所以是prvalue expression.
    ...

 

值的规则和关系

value_categories.png

glvalue

have identity
有变量名

 

rvalue

can be moved from
可以作为参数传递给func(T&&)的值

 

延申阅读

吐槽
耗时两周左右, 花里胡哨写了这么多,查了很多资料,也在stackoverflow上问了两个相关的问题. 深刻感受到C++对新手的不友好.
这里面最复杂的并不是技术术语和概念,而是这些像是谚语一样的定义和没有充足的具体例子来阐述这些术语.
难点
xvalue expression have identity, 查了很多资料,没有直说,但是最接近这个说法的就是这个例子.

#include <iostream>
using std::cout;
using std::endl;

struct X {
    int i;
};

int main(void) {
    # X().i 是一个 xvalue expression.
    # X() 是一个 prvalue expression, 即没有一个变量名, 你也不能通过 & 来获取它的地址.
    # 但是你可以调用这个作用域里面的 i 变量名.
    # 但是你不能通过 & 来获取 X().i 的地址.
    foo(X().i);              

    return 0;
}

移动语义
在C++11之前只有 lvalue expressionrvalue expression, C++11为了支持移动语义才引入的 xvalue expression.
移动语义为了解决复制问题, 就像python中的 deep copyshallow copy 一样.
还解决另外一个问题就是多级传递, std::forward, 即: 将一个参数的typevalue category 向子函数原封不动的传递.

参考

value category
xvalue example
提交问题1
提交问题2
youtube视频
C++ lvalue,prvalue,xvalue,glvalue和rvalue详解(from cppreference)
Lvalues and Rvalues in C++
What are rvalues, lvalues, xvalues, glvalues, and prvalues?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 基本概念 1a general-purpose programming language用于创建计算机程序。艺术类...
    伍帆阅读 1,304评论 0 1
  • 现在孕30+周啦,和老公都期待小家伙的到来。 虽然现在每天被小家伙闹腾的睡不好,也算幸福的“折磨”吧。每天和TA互...
    Zoe君阅读 216评论 0 1
  • 2018年5月8日下午:,我校对期中考试中成绩优异的学生进行了表彰。我校这次表彰没有给学生颁发奖品,而是给孩子们发...
    坚强_bf5b阅读 268评论 0 0
  • 作者/王洪波 春风,吹绿了世界,万物生机,而自己却无声地消失在无垠的天际。教师就是吹拂学生...
    海伦088王洪波阅读 487评论 0 0
  • 天,要么蓝色,要么灰色。土,要么赤色,要么黄色。而树叶,不一定是绿色。 男人,要么是强大的,要么是软弱的。女人,要...
    李一十八阅读 630评论 1 2