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 
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 
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 
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 
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 
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 
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?

你可能感兴趣的:(C++值类别 Value-categories)