C ++ 0x series: Right Value reference

Source: Internet
Author: User
Right-value reference (and supported move semantics and perfect forwarding) is one of the most important language features C ++ 0x will add, this can be seen from the proposal of this feature ranking first in the C ++-state of the evolution list. From a practical perspective, it can perfectly solve the temporary object efficiency problem that has been criticized for a long time in C ++. In terms of language itself, it improves the defects of the reference type in C ++ in the left and right values. From the perspective of the Database Designer, it brings a powerful tool to the Database Designer. From the perspective of database users, the "free" efficiency can be improved without moving to the ground...

  Move Semantics

Return value efficiency problem -- Return Value optimization (n) RVO) -- mojo facility -- workaround -- problem definition -- move semantics -- language support

Howard hinnant wrote a great tutorial (a.k. A. Proposal n2027). In addition, the original proposals on rvalue-reference were quite readable. Therefore, if you want to understand rvalue-reference, or go to the C ++ Standards Committee website to view the series of proposals (see references at the end of the article ). Or read this article.

  Source

I have read historical records?

The story is a simple introduction... One day, Xiao Xiaofeng blew the wind, in a hand not to see the night of the five fingers...

I used the const reference to accept the parameter, but swallowed the temporary variable. I used non-const references to accept parameters, but dropped the left value of Const. As a result, I am looking for solutions in every corner of the standard! I was defeated by 8.5.3 !...

Imagine such a piece of code (since it is similar, it is taken directly from Andrei's famous article ):

STD: vector <int> V = readfile ();

Readfile () is defined as follows:

STD: vector <int> readfile ()
{
STD: vector <int> retv;
... // Fill retv
Return retv;
}

This code is inefficient because of the temporary object returned. A whole vector must be copied once, just to pass a group of int values. After the V is constructed, the temporary object disappears.

This is a massive waste!

What's worse, in principle, there are two waste items here. 1. retv (retv disappears after readfile ). 2. The returned temporary object (the returned temporary variable will immediately disappear after the V copy structure is complete ). However, for the simple code above, most compilers have been able to optimize the two objects and directly create the retv to the object that accepts the returned value, that is, V.

In fact, the efficiency of temporary objects has always been a widely criticized issue in C ++. This problem is so famous that the standard does not hesitate to sacrifice the original concise copy semantics, in section 12.8 of the standard, the limit allows optimization of the copies generated during the function return process (even if the copy constructor has side effects, it will not be spared !). This is the so-called "Copy elision ".

Why (n) RVO (named) Return Value optimization) is almost equivalent

Or, according to Andrei, As long as readfile () is changed to this:

... Readfile ()
{
If (/* err condition */) return STD: vector <int> ();
If (/* yet another err condition */) return STD: vector <int> (1, 0 );
STD: vector <int> retv;
... // Fill retv
Return retv;
}

In this case, the compiler generally gives up optimization.

But for the compiler, this is not the most depressing situation, the most depressing is:

STD: vector <int> V;
V = readfile (); // assignment, not copy Construction

This structure is changed from copy to copy assignment. With a blinking eye, the old hen becomes a duck. The compiler can only surrender. Because the standard only allows (n) RVO in the case of copy construction.

  Why is the library solution not a business experience?

C ++ ghost Andrei Alexandrescu is famous for its deep mining and utilization of C ++ standards, as early as 03 years ago (the so-called efficiency problem of temporary variables had been in the news group for a while, and the related language-level solutions had already appeared in February September) under the existing standard (C ++ 98), it is hard to find a solution that can solve the problem by 100%.

Andrei calls this framework mojo, just like a layer of talcum powder, and spreads it onto the existing class... Guess what it is, no, it's not "getting the cursor Without A Trace": P, it's a problem with the efficiency of this type of temporary object!

The only problem with mojo is that the usage is too complicated. This complexity is largely due to a wording problem in the standard (the C ++ standard is like this. The ghost knows which corner of a sentence can bring out a brilliant solution. At the same time, the ghost knows which corner of the sentence can erase an originally concise solution ). This is the 8.5.3 problem I mentioned earlier. It has been resolved by core language issue 391.

For the database solution, solving the problem is of course the first priority. However, it is an invasive solution, and the complexity of the use of the outer band will inevitably not be far from that. Therefore, although we do not deny that mojo is a genius solution, it is difficult to use it. This is why mojo is not industrialized.

Why is it true that you use reference to transmit parameters?

Void readfile (vector <int> & V ){... // Fill v}

Of course.

But what if we encounter Operator Overloading?

String operator + (string const & S1, string const & S2 );

In addition, even for readfile, the original version of the returned vector is supported.

Boost_foreach (int I, readfile ()){
... // Do something. With I
}

After being changed to reference parameter passing, the original elegant form was damaged. In order to perform the above operation, we had to introduce a new name, which existed only to cope with the damaged form, once the foreach operation ends, its short life also ends:

Vector <int> V;
Readfile (v );
Boost_foreach (int I, V ){
}

// V becomes useless here

Is there any problem? Discover it by yourself. In short, the use of reference parameter passing is a solution, but its capabilities are limited, and it will also bring some other problems. After all, it is not an elegant method.

  What is the problem?

Is your light on? It clearly states the importance of defining "what is the problem. This issue is equally important for the efficiency of our temporary objects.

In short, the problem can be described:

C ++ does not distinguish between copy and move semantics.

What is move semantics? Do you remember auto_ptr? Auto_ptr is not strictly copied during "copy. "Copy" means to keep the source object unchanged and copy a new object based on it. However, the "copy" of auto_ptr will empty the source object, leaving only one shell-one transfer of resource ownership.

This is move.

  The role of moving semantics-Efficiency Optimization

For example, the STD: string copy constructor will do two things: 1. Allocate a buffer with an appropriate size based on the source STD: String object size. 2. Copy the string from the source STD: string.

// Just for lower strating the idea, not the actual implementation

String: string (const string & O)
{
This-> buffer _ = new buffer [O. Length () + 1];
Copy (O. Begin (), O. End (), buffer _);
}

But suppose we know that O is a temporary object (for example, the return value of a function), that is, O will not be used elsewhere, if the life cycle of o ends at the end of its full expression, we can steal the resources in O:

String: string (temporary string & O)
{
// Since o is a temporary, we can safely steal its resources without causing any problem
This-> buffer _ = O. Buffer _;
O. Buffer _ = 0;
}

The temporary here is a fabricated keyword. Its function is to make the constructor partition a temporary object (that is, only when the parameter is a temporary String object, this constructor is called ).

Think about it. If such a moving constructor exists, the copy construction behavior of all source objects as temporary objects can be simplified to moving structures. For the string example above, the efficiency difference between move and copy construction is to save one O (n) allocation operation and one O (n) copy operation, one O (1) destructor (the structure of the temporary object to be copied ). The efficiency improvement here is obvious and significant.

Finally, to achieve this, we only need to have the ability to judge the right value of the Left value (such as the temporary keyword we previously imagined ), this allows you to steal resources when the source object is a temporary object.

  The role of moving semantics-enabling)

Another example is STD: fstream. Fstream cannot be copied (in fact, all standard stream objects cannot be copied). Therefore, we can only access the stream object created at the beginning through reference. However, there is a problem with this method. If we want to return a stream object from a function, it will not work:

// How do we make this happen?
STD: fstream createstream ()
{... }

Of course, you can use auto_ptr to solve this problem, but this makes the code very clumsy and difficult to maintain.

However, if fstream is moveable, the above Code is feasible. The so-called "moveable" refers to the actual action performed under the object copy syntax (when the source object is a temporary object) is transfer of resource ownership like auto_ptr: the source object is hollowed out, all resources are transferred to the target object-like a move ). After the move operation, although the source object still exists with its name and surname, its "substance" (internal resources) has actually disappeared, or, the source object has disappeared from semantics.

For a moveable but not copyable fstream object, when a move occurs (for example, in the code above, when a partial fstream object is moved out of the createstream () function ), there will be no two copies of the same object. Instead, the identity of the source object of the move disappears, and this identity is re-held by the temporary fstream object returned. That is to say, the uniqueness (non-copyable) of fstream is respected.

You may ask, isn't it a problem if the empty source object is used again? Yes. This is why we should move an object only when we need it and can move it, for example, in the last row (return) of the function) the statement moves a partial vector object (return STD: Move (V). Because this is the last line of the statement, the following v cannot be used again, for it, the rest of the operation is the structure, so it is entirely appropriate to be hollowed out in terms of semantics.

Initial example-perfect solution

In the previous example

Vector <int> V = readfile ();

With the intention of moving, readfile can be simply changed:

STD: vector <int> readfile ()
{
STD: vector <int> retv;
... // Fill retv
Return STD: Move (retv); // move retv out
}

STD: Move. Currently, you only need to know That STD: Move can empty retv, that is, move out, and the final destination of the move is v. In this case, from the perspective of memory allocation, only the memory allocation in retv is in the process from retv to the returned temporary object, and then from the latter to the "move" of the destination V, there is no memory allocation (I mean the Buffer Allocation in the vector). Instead, the buffer in the retv is "transferred" to the returned temporary object, and then transfer from the temporary object to v. Compared with the previous two copies, how much work is saved by the two move operations? It saves two new operations and two delete operations, and two O (n) copy operations. The overall cost of these operations is proportional to the size of the retv vector. It is no wonder that the efficiency of temporary objects is one of the cancers of C ++. It is no wonder that the C ++ standard will allow (n) RVO at any cost.

 How to support moving Semantics

According to the previous introduction, you must already know. The most important part of moving semantics is the ability to distinguish the left and right values during compilation (that is, identifying temporary objects ).

Now, let's recall that at the beginning of the article I mentioned:

I used the const reference to accept the parameter, but swallowed the temporary variable. I used non-const references to accept parameters, but dropped the left value of Const. As a result, I am looking for solutions in every corner of the standard! I was defeated by 8.5.3 !...

Why?

  Scheme under the current standard (C ++ 03)

To distinguish the right value from the left value, you can only reload it:

Void Foo (x const &);
Void Foo (X &);

This overload obviously does not work. Because X const & will swallow the non-const temporary object together.

The problem with this approach is. X & is a non-const reference. It can only accept the left value of Non-Const. However, there are four combinations of values in C ++:

Const non-const
Lvalue
Rvalue

Const-ness is orthogonal to lvalue-ness.

A non-const reference can only be bound to one of the combinations, that is, non-const lvalue. The left const value, right const value, and non-const value that we are most concerned about. Only the last -- Non-const right value -- can be moved.

The remaining problem is how to design overload functions to get the Left and Right const values of Const. So that only the non-const right value is left.

Fortunately, we can use the powerful template parameter derivation mechanism:

// Catch non-const lvalues
Void Foo (X &);
// Catch const lvalues and const rvalues
Template <typename T>
Void Foo (T &, enable_if_same <t, const x >:: type * = 0 );
Void Foo (/* what goes here? */);

Note that the second overload is responsible for accepting the const left and const right values. After the first and second Foo reloads, the rest is non-const rvalue.

The problem is, how do we capture these non-const rvalues? According to C ++ 03, the const-const rvalue can only be bound to the const reference. However, if we use the const reference, we will accept the const left and right values together (because in the template function (the second overload) and the non-template function (the third overload) compilers always prefer non-templates ).

In addition to const reference, is there any way to accept a non-const rvalue?

Yes.

If your type is X, you only need to add a bit of material to X:

Struct ref_x
{
Ref_x (x * P): P _ (p ){}
X * P _;
};
Struct x
{
// Original stuff
...
// Added stuff, for move semantic
Operator ref_x ()
{
Return ref_x (this );
}
};

In this way, our third overload function can be written:

Void Foo (ref_x RX); // accept non-const temporaries only!

Bang! We successfully identified the non-const temporary object of moveable in C ++ 03. But the premise is that you must add something in the moveable type. This is also the biggest drawback of the solution-it is intrusive (not to mention that it uses the dark corners of the language and brings a lot of coding complexity ).

  C ++ 09 Solution

As a matter of fact, this overload-loaded solution is used as the mojo framework of Andrei. Although the mojo framework is exquisite, it is too complex, expensive to use, and not elegant and intuitive. Therefore, language-level support seems to be an inevitable choice (you will see later that the new language features introduced to support moving semantics also support another broad problem-perfect forwarding ).

The reason why C ++ 03 is annoying is that it does not have a reference type to bind to the right value, but instead uses the const left value reference to replace it. It turns out that this privilege is not a long term, after 10 years, we still need to improve the quote semantics.

C ++ 09 adds a new reference type-right value reference. The feature of the right value reference is that it is first bound to the right value. Its syntax is as follows "). With the right value reference, we can simply modify the preceding solution:

Void Foo (x const & X );
Void Foo (X & X );

In this way, both the left value and the right value of const are bound to the first overloaded version. The remaining non-const right values are bound to the second overloaded version.

For your moveable Type X, it is like this:

Struct x
{
X ();
X (x const & O); // copy constructor
X (X & O); // move Constructor
};

X source ();
X = source (); // #1

At #1, the call will be X: X (X & O), that is, the so-called move constructor, because source () the returned result is a temporary object (non-const right value). The move constructor is selected in the heavy-load resolution.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.