Always want to try to understand and learn the right value of the relevant technical details to organize and share, hope to be able to help interested friends.
Rvalue references are a new feature in the C++11 standard. Rvalue references allow programmers to ignore logically unwanted copies, and can also be used to support functions that implement perfect forwarding. They are all libraries that enable more efficient and robust.
Move semantics
The specific rvalue reference definition is not expanded first. Let's talk about move semantics. Rvalue references are used to support the move semantics. Move semantics refers to moving a resource in object A of the same type (either on a heap or a file handle or other system resource) to another object B of the same type, releasing object A's ownership of the resource. This reduces the construction, copying, and destruction of unnecessary temporary objects. For example, we often use the std::vector<t>, when two of the same std::vector type assignment, the general steps are as follows:
- The internal assignment constructor typically allocates the specified size of memory first,
- Copy from the source std::vector to the newly requested memory,
- Then the original object instance is reconstructed,
- Finally take over the data of the new application.
This is the copy semantics we used before c++11, which is often called deep copy. Move semantics are relative to copy semantics, similar to a shallow copy, but the ownership of resources has shifted. The implementation of move semantics can reduce the copy action and greatly improve the performance of the program.
In order to realize the construction of move semantics, we need the corresponding syntax to support it. The original copy constructor is not sufficient to meet this requirement. The most typical example is c++11 abandoned Std::auto_ptr, whose constructors generate ambiguous ownership relationships and can easily breed bugs. That's why many people don't like std::auto_ptr. C++11 adds the corresponding constructor for this purpose.
class Foo {public: foo (foo&& f) {} foooperatorf) { return *this; };
Here you can see clearly that the parameter types in the two functions are foo&&. This is the basic syntax for rvalue references. The purpose of this is to implement different function handling through function overloading.
Force move semantics
The c++11 rule allows you to use the move semantics on the right value, or you can use the move semantics on the left value. That is, you can convert an lvalue to an rvalue reference and then use the move semantics. For example, in the classic function swap of C + +:
template<class t>void swap (t& A, t& b) { T tmp (a); = B; = tmp;} X A, B;swap (A, b);
There is no right-hand value in the above code, but the TMP variable only works in the scope of the function, only to assume the data transfer action. The above-mentioned rules developed by C++11 can be very well applied here instead. C++11 in order to achieve this rule, the Std::move function is implemented, and the function is to convert the passed arguments to an rvalue reference and return. That is to say, under C++11, the swap is implemented as follows:
template<classvoid swap (t& A, t& b) { T tmp (Std::move (a)); = Std::move (b); = Std::move (tmp);} X A, B;swap (A, b);
We can use Std::move as much as possible in actual use. Only our custom types are required to implement the transfer constructor.
Rvalue reference
To clarify what rvalue refers to, you have to say Lvalue and rvalue. Simply put, the lvalue is an expression that points to a memory space, and we can use the & operator to get the address of that memory space. An rvalue is an expression that is a non-lvalue value. You can read this article "Lvalues and Rvalues" for a deeper understanding.
An rvalue reference is very similar to a normal reference for C + + and is also a composite type. For ease of distinction, a normal reference is an lvalue reference. An lvalue reference is the addition of the & operator after the type. The Rvalue reference is the addition of the && operator after the type, just like the parameters of the transfer constructor above.
An rvalue reference behaves like an lvalue reference, but an rvalue reference can only bind a temporary object and cannot bind an lvalue reference. The appearance of rvalue references also affects the function overload resolution. The left value overrides the function of the Lvalue reference parameter, and the right value overrides the function of the Rvalue reference parameter:
void // Lvalue Reference Overload void // rvalue Reference Overload // argument is lvalue:calls foo (x&)// argument is rvalue:calls foo (x& &)
Theoretically, you can overload any function in this way, but in most cases such overloads only appear in the copy constructor and assignment operators, that is, implementing the move semantics.
If you implement void Foo (x&), but you do not implement void Foo (x&&), then the parameter Foo as before can only be an lvalue. if void foo (X const &) is implemented, but void Foo (x&&) is not implemented, as before, Foo's parameters can be either Lvalue or rvalue. The only way to differentiate between left and right values is to implement void Foo (x&&). Finally, if the implementation of void Foo (x&&) is implemented, but the void foo (x&) is not implemented, and void foo (X const &), then Foo's argument will only be the right value.
is an rvalue reference a right-hand value?
void foo (x&& x) { = x; // ...}
In the above function foo, which constructor of X is called? Is it a copy construction or a transfer construct? As we said earlier, this is an rvalue reference, which should be called X (x&&); But in fact, this is called the X (const x&); This is a confusing place: an rvalue reference type can be either an lvalue or an rvalue, judging by whether the Rvalue reference has a name. A name is a left-hand value, otherwise it is the right value. If you want to turn rvalue references with names to rvalue, you need to use the Std::move function.
void foo (x&& x) { = std::move (x); // ...}
Some people do not understand this when implementing their own transfer constructors, resulting in the fact that the copy constructor is actually executed within the implementation of their own transfer constructor.
Move semantics and return value optimization
Having learned some of the concepts of move semantics and forced move and rvalue references, some friends implement some functions by forcing a move where they are returned. Think that this can reduce the copy one time. Like what:
x foo () { x x; // perhaps do something to x return // making it worse!}
In fact, this is not necessary. Because the compiler will do the return value optimization (Return of value optimization). This means that the compiler can perceive that the x variable is no longer needed, and it needs to be transferred outside the function.
Perfect forwarding
Rvalue references are designed to solve the problem of perfect forwarding, in addition to implementing the move semantics. We sometimes write factory functions, such as the following code:
Template<typename T, TypeName arg> shared_ptr<T> Factory (Arg arg) { return Shared_ptr<t> (new T (ARG));
This implementation is very simple, that is, the parameter arg is passed to class T for construction. But this introduces additional function calls by value, not to constructors that use references as arguments.
So in order to solve this problem, some people think of using references, such as:
Template<typename T, TypeName arg> shared_ptr<T> Factory (arg& Arg) { return shared_ptr<t> (new T (ARG));
But there's a problem here, and you can't receive an rvalue as a parameter.
// error if Hoo returns by valuefactory<x> ($// error
The workaround is to continue to introduce the const reference. If there are multiple parameters, the argument list of the function becomes more disgusting. Another problem is that you can't implement move semantics.
The Rvalue reference solves this problem by not using overloaded functions to achieve true perfect forwarding. But it needs to match the rules of two rvalue references:
- Referencing overlay Rules
a& & = a&a& && = a&a&& & = a&a&& Amp && = a&&
- Template parameter Derivation rules
Template<typename t>void foo (t&&);
When the argument of a function foo is an lvalue of type A, the type of T is a&. Then judging by the reference overlay rule, the actual type of the last parameter is a&.
When the argument of Foo is an rvalue of type A, the type of T is a. According to the reference overlay rule, the final type is a&&.
With these rules, we can use rvalue references to solve the perfect forwarding problem in front of you. Here's how to fix it:
Template<typename T, TypeName arg> shared_ptr<T> Factory (arg&& Arg) { Return shared_ptr<t> (new T (std::forward<arg>(ARG)));
The implementation of Std::forward is as follows:
template<class s>S&& forward (TypeName remove_reference<s>::type& a) noexcept{ return static_cast<s&&>(a);}
It is clear that the above two rules can be understood without a concrete example. It is recommended to read Scott Meyers's "Universal References in C++11".
Reference documents
- "C + + Rvalue References Explain"
- "Universal References in C++11"
- "A Brief Introduction to Rvalue References"
Summarize
The appearance of rvalue references may seem to add additional complexity, but the benefits are very obvious, which can help implement the move semantics, improve the performance of the program, and achieve perfect forwarding, which facilitates the design of the library.
C + + is like this, give you a new feature, it will also bring additional learning difficulties. But that's why many people like C + +, which gives programmers too much of a chance. It is an essential tool for high performance programs to control the life cycle of objects precisely.
Analysis of C + + rvalue reference