C++左值和右值

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

         C++中的每个表达式都有两种独立的特性:类型(type)和值分类(value category)。每个表达式都属于三大value category之一:prvalue,xvalue和lvalue。值分类的隶属关系如下图:

 

         a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity of an object, bit-field, or function;

         glvalue,泛左值,它的求值决定了一个对象,位域或函数的“身份”(identity);

a prvalue (“pure” rvalue) is an expression whose evaluation either:

computes the value of the operand of an operator (such prvalue has no result object);

initializes an object or a bit-field (such prvalue is said to have a result object). All class and array prvalues have a result object even if it is discarded. In certain contexts, temporary materialization occurs to create a temporary as the result object;

prvalue,纯右值。它的求值,要么是计算一个运算符操作数的值(这样的prvalue没有结果对象,也就是其值不会存储于对象中);要么是初始化一个对象或位域(这样的prvalue具有值对象,也就是其值会存储于对象中),所有的类prvalues和数组prvalues都会有一个值对象,尽管其会被抛弃。在特定场景下,会发生temporary materialization用于创建一个临时值对象。Temporary materialization的解释如下:

A prvalue of any complete type T can be converted to an xvalue of the same type T. This conversion initializes a temporary object of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. If T is a class or array of class type, it must have an accessible and non-deleted destructor.

struct S { int m; };
int i = S().m; // member access expects glvalue as of C++17;
               // S() prvalue is converted to xvalue

 Temporary materialization occurs in the following situations:

when binding a reference to a prvalue;

when performing a member access on a class prvalue;

when performing an array-to-pointer conversion (see above) or subscripting on an array prvalue;

when initializing an object of type std::initializer_list<T> from a braced-init-list;

when typeid is applied to a prvalue (this is part of an unevaluated expression);

when sizeof is applied to a prvalue (this is part of an unevaluated expression);

when a prvalue appears as a discarded-value expression.

an xvalue (an “eXpiring” value) is a glvalue that denotes an object or bit-field whose resources can be reused;

xvalue,将亡值。是一个glvaue,表示一个其资源可被重用的对象或位域;

an lvalue (so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) is a glvalue that is not an xvalue;

lvalue,左值。是glvaule中不属于xvalue的那部分;

an rvalue (so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is a prvalue or an xvalue.

rvalue,右值。是一个prvalue或一个xvalue。

下面的是lvalue表达式:

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;(即使变量类型是一个右值引用,其名字形成的变量名也是一个左值)

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;

a = b, a += b, a %= b, and all other built-in assignment and compound assignment expressions;

++a and --a, the built-in pre-increment and pre-decrement expressions;

*p, the built-in indirection expression;

a[n] and p[n], the built-in subscript expressions, where one operand in a[n] is an array lvalue;

a.m, the member of object expression, except where m is a member enumerator or a non-static member function, or where a is an rvalue and m is a non-static data member of non-reference type;

p->m, the built-in member of pointer expression, except where m is a member enumerator or a non-static member function;

a.*mp, the pointer to member of object expression, where a is an lvalue and mp is a pointer to data member;

p->*mp, the built-in pointer to member of pointer expression, where mp is a pointer to data member;

a, b, the built-in comma expression, where b is an lvalue;

a ? b : c, the ternary conditional expression for some b and c (e.g., when both are lvalues of the same type, but see definition for detail);

a string literal, such as "Hello, world!";

a cast expression to lvalue reference type, such as static_cast<int&>(x);

a function call or an overloaded operator expression, whose return type is rvalue reference to function;

a cast expression to rvalue reference to function type, such as static_cast<void (&&)(int)>(x).

下面是prvalue表达式:

a literal (except for string literal), such as 42, true or nullptr;

a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++;

a++ and a--, the built-in post-increment and post-decrement expressions;

a + b, a % b, a & b, a << b, and all other built-in arithmetic expressions;

a && b, a || b, !a, the built-in logical expressions;

a < b, a == b, a >= b, and all other built-in comparison expressions;

&a, the built-in address-of expression;

a.m, the member of object expression, where m is a member enumerator or a non-static member function[2], or where a is an rvalue and m is a non-static data member of non-reference type (until C++11);

p->m, the built-in member of pointer expression, where m is a member enumerator or a non-static member function[2];

a.*mp, the pointer to member of object expression, where mp is a pointer to member function[2], or where a is an rvalue and mp is a pointer to data member (until C++11);

p->*mp, the built-in pointer to member of pointer expression, where mp is a pointer to member function[2];

a, b, the built-in comma expression, where b is an rvalue;

a ? b : c, the ternary conditional expression for some b and c (see definition for detail);

a cast expression to non-reference type, such as static_cast<double>(x), std::string{}, or (int)42;

the this pointer;它的值就是对象地址。

an enumerator;

a lambda expression, such as [](int x){ return x * x; };

(since C++11)

a requires-expression, such as requires (T i) { typename T::type; };

a specialization of a concept, such as EqualityComparable<int>.

下面是xvalue表达式:

a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);

a[n], the built-in subscript expression, where one operand is an array rvalue;

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.*mp, the pointer to member of object expression, where a is an rvalue and mp is a pointer to data member;

a ? b : c, the ternary conditional expression for some b and c (see definition for detail);

a cast expression to rvalue reference to object type, such as static_cast<char&&>(x);

any expression that designates a temporary object, after temporary materialization.(since C++17)

rvalue表达式要么是一个prvalue,要么是一个xvalue。它的性质有:

不能对其取地址;它不能出现在赋值运算符(包括复合赋值运算符)的左侧;可以用rvalue初始化const lvalue引用,这种情况下,该rvalue对象的生命期得到了延长;可以用rvalue初始化rvalue引用,这种情况下,该rvalue对象的生命期也得到了延长;

当用rvalue调用函数时,如果函数有两个重载,一个接收rvalue引用,另一个接收const lvalue引用,则会调用那个接收rvalue引用的重载;

With the introduction of move semantics in C++11, value categories were redefined to characterize two independent properties of expressions[5]:

随着C++11引入了移动语义,重新定义了value categories,以便可以描述表达式的两种独立属性:

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 the functions they identify (obtained directly or indirectly);

有身份的:可以判断表达式是否与其他表达式表示的是同一实例,比如可以通过对比对象或函数的地址;

can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.

可移动的:表达式可以使用于移动语义,比如移动构造函数、移动赋值操作符,或者其他实现了移动语义的函数。

In C++11, expressions that:

have identity and cannot be moved from are called lvalue expressions;

有身份,不能移动的,可以称之为lvalue表达式;

have identity and can be moved from are called xvalue expressions;

有身份,也能移动的,可以称之为xvalue表达式;

do not have identity and can be moved from are called prvalue ("pure rvalue") expressions;

没有身份,可以移动的,可以称之为prvalue表达式;

do not have identity and cannot be moved from are not used[6].

没有身份;不可移动的表达式不存在,无意义;

The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.

有身份的表达式称为glvalue表达式。lvalue和xvalue都是glvalue表达式

The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.

可以移动的表达式称为rvalue表达式,prvalue和xvalue都是rvalue表达式

http://gulu-dev.com/post/2016-02-07-lvalue-rvalue

来自 Scott Meyers 的方法:判断表达式是否是左值,有一个简单的办法,就是看看能否取它的地址,能取地址的就是左值。A useful heuristic to determine whether an expression is an lvalue is to ask if you can take its address. If you can, it typically is. If you can’t, it’s usually an rvalue. A nice feature of this heuristic is that it helps you remember that the type of an expression is independent of whether the expression is an lvalue or an rvalue. -- "Effective Modern C++", Introduction - Terminology and Conventions, Scott Meyers

https://josephmansfield.uk/articles/lvalue-rvalue-metaphor.html

来自 Joseph Mansfield 的方法:

Lvalues represent objects and rvalues represent values.

左值代表“对象”,右值代表“值”;

Lvalue-to-rvalue conversion represents reading the value of an object.

左值到右值的转换可看做“读出对象的值”

std::move allows you to treat any expression as though it represents a value.

std::move 允许把任何表达式以“值”的方式处理

std::forward allows you to preserve whether an expression represented an object or a value.

std::forward 允许保留表达式为“对象”还是“值”的特性

Every useful C++ program revolves around the manipulation of objects, which are regions of memory created at runtime in which we store values. A simple int x;, for example, creates an object for storing integer values.

任何一个有价值的 C++ 程序都是如此:a) 反复地操作各种类型的对象 b) 这些对象在运行时创建在明确的内存范围内 c) 这些对象内存储着值。比如int x; 创建了一个对象用于存储整型值

We also come across values that do not belong to any particular object. For example, the literal 5 represents the abstract value of 5, but is not stored in any object. Similarly, if we have two int objects, x and y, the expression x + y gives us a value representing the result of the addition — this value is also not stored in an object.

A simple interpretation of lvalues and rvalues is that lvalues represent objects and rvalues represent values. In the following code, x denotes an object, so it's an lvalue. x + 5 denotes a value, so it's an rvalue. The subexpression 5 is also an rvalue.

void foo(int x) {
    bar(x); // the argument is an lvalue expression
    bar(x + 5); // the argument is an rvalue expression
}

  

I'm being careful here by using the word "represent". The truth is that rvalue expressions can denote objects too, but they still represent values. For example, some rvalue expressions result in the creation of a temporary object — such as a function call that returns by value. Although an object does really exist here, the expression can still be thought of as just representing a value of that type. Consider this function:

std::string get_message() {
    return "Hello, World!";
}

 Else where in your code, the function call get_message() denotes the value of an std::string containing "Hello, World!", rather than a persistent object that you are going to manipulate.

Technically these are two kinds of rvalue: expressions denoting truly abstract values are prvalues (pure), while expressions denoting short-lived objects are called xvalues (expiring)

Most operators in C++ expect rvalues (values) as their operands. If we want to perform addition, for example, we just need two values to add together — we don't care if they belong to objects. A notable exception is the assignment operator, which requires an lvalue (object) for its left operand. This is also logical — assignment needs an object in which to store something.

We can, however, also use lvalues where rvalues are expected — this is permitted by the implicit lvalue-to-rvalue conversion. Once again, this makes sense — if we provide an object where a value is expected, we can just read the value of the object. That is, lvalue-to-rvalue conversion represents reading the value of an object..

Lvalue-to-rvalue conversion actually converts both lvalues and xvalues to prvalues. As we saw, an xvalue also denotes an object behind the scenes so we have to read its value too. Lvalues and xvalues are collectively known as glvalues (general).

Both std::move and std::forward give you super powers: the ability to manipulate the value category of an expression.

A call to std::move is always an rvalue (value) expression. Because of this, std::move allows you to treat any expression as though it represents a value. What's the purpose of this? Well, objects are persistent regions of storage that we don't expect to change when doing non-destructive operations on them. However, if we know that we don't need the object any longer, we can often use destructive yet more efficient implementations. Values are inherently transient, so treating an object like a value allows us to perform these more efficient operations. For example, by treating objects as values, we can efficiently steal their resources when copying them (which we call moving, rather than copying). Look up move semantics to find out how to implement this for your classes.

In some cases, C++ will silently do this, treating your lvalues as rvalues (as though you had std::moved them). For example, when returning a local object from a function, the compiler knows that the object is no longer required and so can treat the returned expression as though it just represents a transient value:

widget foo() {
    widget w;
    // ...
    return w; // the expression w is an lvalue, but is treated as an rvalue
}

  

std::forward relies on a neat little trick involving type deduction and reference collapsing. Consider the following example:

template<class T>
void wrapper(T&& x) {
    foo(std::forward<T>(x));
}

 When the argument passed to wrapper is an lvalue expression of type widget, x is deduced to be of type widget&. When it is an rvalue expression, x is of type widget&&. In both cases, the expression x will just be an lvalue. However, the std::forward function is cleverly designed so that std::forward(x) is an lvalue in the first case and an rvalue in the second case. Therefore, std::forward allows you to preserve whether an expression represented an object or a value.

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

右值引用可以用于延长临时对象的生命期(const 左值引用也可以,但是它是不能修改的)

std::string s1 = "Test";
std::string&& r1 = s1; // error: can't bind to lvalue

const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
r2 += "Test"; // error: can't modify through reference to const

std::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetime
r3 += "Test"; // okay: can modify through reference to non-const
std::cout << r3 << '\n'; //TestTestTest

  

更重要的是,如果重载函数,既有接收右值引用的版本,又有接收左值引用的版本,则使用右值(包括prvalue和xvalue)调用函数会调用到右值引用的版本,而左值会调用到左值引用的版本

void f(int& x) {
    std::cout << "lvalue reference overload f(" << x << ")\n";
}
 
void f(const int& x) {
    std::cout << "lvalue reference to const overload f(" << x << ")\n";
}
 
void f(int&& x) {
    std::cout << "rvalue reference overload f(" << x << ")\n";
}
    
int i = 1;
const int ci = 2;
f(i);  // calls f(int&)
f(ci); // calls f(const int&)
f(3);  // calls f(int&&)
       // would call f(const int&) if f(int&&) overload wasn't provided
f(std::move(i)); // calls f(int&&)

// rvalue reference variables are lvalues when used in expressions
int&& x = 1;
f(x);            // calls f(int& x)
f(std::move(x)); // calls f(int&& x)

  

因为右值引用可以绑定到xvalue,因此他们也可以指向非临时对象:

int i2 = 42;
int&& rri = std::move(i2); // binds directly to i2

 这使得将不再有用的对象进行移动称为可能:

std::vector<int> v{1,2,3,4,5};
std::vector<int> v2(std::move(v)); // binds an rvalue reference to v
assert(v.empty());

猜你喜欢

转载自www.cnblogs.com/gqtcgq/p/9828247.html