This article is the first page of Part 2
What I'm going to talk about today is the rvalue references (right value reference), which implements two different things: move semantics and perfect forwarding. They are difficult to understand at first because they need to differentiate between lvalues and rvalues, and only a handful of c++98/03 programmers are familiar with it. This article will be very long because I intend to explain in great detail the workings of rvalue references.
Don't be afraid, using ravlue references is easy, much easier than it sounds. To implement move semantics or perfect forwarding in your code, just follow the simple pattern, which I'll demonstrate later. Learning how to use the Rvalue references is definitely worthwhile because move semantics can bring a huge performance boost, and perfect forwarding makes it easy to write highly generic code.
Lvalues and Rvalues in C + + 98/03
To understand the rvalue references in C + + 0x, you first have to understand the lvalues and rvalues in C + + 98/03.
The terms "lvalues" and "rvalues" are easy to confuse because their historical origins are also confusing. (Incidentally, they are pronounced "L values" and "R values", although they are all written in a single word). The two concepts originally came from C and were later played in C + +. To save time, I skipped the history of them, such as why they are called "lvalues" and "rvalues," and I'll talk directly about how they work in C + + 98/03. (Well, it's not a big secret: "L" means "left", "R" means "right.") Their meaning has been evolving and the name has not changed, and now the name is not a "real". Instead of helping you with a history lesson, feel free to think of them as "up quarks" and "down quarks" and nothing to lose. )
C + + 03 standard 3.10/1 said: "Each expression is either a lvalue, or a rvalue." "You should keep in mind that Lvalue and rvalue are for expressions, not objects.
Lvalue refers to persistent objects that remain after the end of a single expression. For example: Obj,*ptr, Prt[index], ++x are all lvalue.
Rvalue refers to temporary objects that cease to exist at the end of an expression (at a semicolon). For example: 1729, x + y, std::string ("Meow"), and X + + are rvalue.
Notice the difference between ++x and X + +. When we write int x = 0; , X is a lvalue, because it represents a persistent object. The expression ++x is also a lvalue that modifies the value of X, but represents the original persistent object. However, the expression X + + is a rvalue that simply copies the initial value of a persistent object, modifies the values of the persisted object, and returns that copy, which is a temporary object. Both ++x and X + + are incremented, but ++x returns the persistent object itself, while X + + returns a temporary copy. That's why ++x is a lvalue, and X + + is a rvalue. The difference between Lvalue and rvalue is not what the expression does, but what the expression represents (persistent object or temporary product).
Another way to develop an intuitive sense of whether an expression is lvalue is to ask yourself, "can I take an expression?" "If it can, it is a lvalue; if not, it is a rvalue." For example: &obj, &*ptr, &ptr[index], and &++x are all legal (even if some of the examples are stupid), and &1729, & (x + y), &std::string ("Me Ow "), and &x++ is not legal. Why does this approach work? Because the fetch operation requires its "operand must be a lvalue" (see C + + 03 5.3.1/2). Why should there be such a rule? Because it's okay to access a persistent object, however, it is extremely dangerous to address a temporary object because the temporary object will soon be destroyed (as you have a pointer to an object that is released, but you are still using that pointer, and the ghost knows what the pointer is pointing at this point).
The previous example does not consider the case of operator overloading, it is just a normal function of semantic. "A function call is a lvalue if and only if it returns a reference" (see C + + 03 5.2.2/10). Therefore, the given statement vercor<int> V (10, 1729); , V[0] is a lvalue because the operator [] () returns int& (and &v[0] is legitimately available), while the given statement string s ("foo"); and string T ("Bar");, S + t is a rvalue because The operator + () returns a string (and & (S + t) is also illegal).
Both Lvalue and rvalue have both extraordinary quantities (modifiable, i.e. non-const) and constants (const). For example:
string one("cute");
const string two("fluffy");
string three() { return "kittens"; }
const string four() { return "are an essential part of a healthy diet"; }
one; // modifiable lvalue
two; // const lvalue
three(); // modifiable rvalue
four(); // const rvalue
type& can be bound to a very lvalue (you can use this reference to read and modify the original value), but you cannot bind to the const lvalue, because that would violate the const correctness, or bind it to the very amount of rvalue, which is extremely dangerous, You use this reference to modify the temporary object, but the temporary object is long gone, which leads to difficult to catch and annoying bugs, so C + + wisely prohibits this. (I would add that the VC has an evil extension that is allowed to be so reckless, but if you compile it with parameter/w4, the compiler will usually prompt for "evil extensions are activated"). Nor can it be bound to the const ravlue, because that would be doubly bad. (Attentive readers should note that I am not talking about template parameter derivation here).
The const type& can be bound to: the very amount of lvalues, the const lvalues, the very amount rvalues, and the const values. (then you can use this reference to observe them)
The reference is named, so a reference bound to the rvalue itself is a lvalue (yes!). is L). (because only a const reference can be bound to rvalue, it is a const lvalue). This is confusing, and (not clear) it will be more difficult to understand later, so I will explain it further. Given the function void observe (const string& str), in the implementation of observe (), STR is a const lvalue that can be fetched and used at that address before observe () returns. This is true even if we call observe () by passing a rvalue parameter, like the three () and four () above. You can also call observe ("purr"), which constructs a temporary string and binds Str to that temporary string. The return objects of three () and foure () are not named, so they are rvalue, but in observe () STR is named, so it is a lvalue. As I said earlier, "Lvalue and rvalue are for expressions, not objects". Of course, since STR can be bound to a temporary object that will soon be destroyed, we should not save the address of this temporary object anywhere until observe () returns.
Have you ever taken an address to a const reference bound to Rvalue? Of course, you have! Whenever you write a copy assignment operator with a self assignment check: foo& operator= (const foo& Other), if (this!= &other) {copy Struff;} or copy the assignment from a temporary variable, like: Foo Make_foo (); Foo F; f = Make_foo (); , you have done such a thing.
At this point, you might ask, "What's the difference between a rvalues and a const rvalues?" I can not bind the type& to the very rvalue, nor can I modify the rvalue by assigning operations like this, so could I really modify them? "The question is very good!" In C + + 98/03, there are some subtle differences: Non-constrvalues can invoke Non-const member functions. C + + does not expect you to accidentally modify temporary objects, but it is obvious that the Non-const member function is raised directly in Non-const rvalues, so this is allowed. In C + + 0x, the answer has changed dramatically, and it can be used to implement move semantics.
Congratulations! You already have what I call "Lvalue/rvalue view" so that you can determine at a glance whether an expression is lvalue or rvalue. Plus your previous knowledge of const, you can fully understand why the given statement void mutate (string& ref) and the preceding variable definition, mutate (one) is legal, and mutate (two), mutate (three ()) , Mutate (four ()), mutate ("purr") are illegal. If you are a C + + 98/03 programmer, you can already tell which of these calls are legitimate and which are illegal; it is your "gut instinct", not your compiler, that tells you that mutate (three ()) is counterfeit. Your new understanding of Lvalue/rvalue gives you a clear understanding of why three () is a rvalue and why the very reference cannot be bound to the right value. Do you know how this works? Useful for a language lawyer, but not for ordinary programmers. After all, if you don't understand everything about lvalues and rvalues, it's still far away. But the point is: compared to C + + 98/03, the Lvalue and rvalue in C + + 0x have a broader and stronger meaning (especially to determine whether the expression is Modifiable/const lvalue/rvalue and do some processing accordingly). To effectively use C + + 0x, you also need to have an understanding of lvalue/rvalue. Now that we have everything, we can move on.