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());

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM