The rvalue References

C++11 defines a new type of reference, rvalue references. Prior to C++11, there was no way that code (like an assignment operator) could tell if the rvalue passed to it was a temporary object or not. If such a function is passed a reference to an object, then the function has to be careful not to change the reference because this would affect the object it refers to. If the reference is to a temporary object, then the function can do what it likes to the temporary object because the object will not live after the function completes. C++11 allows you to write code specifically for temporary objects, so in the case of the assignment, the operator for temporary objects can just move the data from the temporary into the object being assigned. In contrast, if the reference is not to a temporary object then the data will have to be copied. If the data is large, then this prevents a potentially expensive allocation and copy. This enables so-called move semantics.

Consider this rather contrived code:

    string global{ "global" }; 

string& get_global()
{
return global;
}

string& get_static()
{
static string str { "static" };
return str;
}

string get_temp()
{
return "temp";
}

The three functions return a string object. In the first two cases, the string has the lifetime of the program and so a reference can be returned. In the last function, the function returns a string literal, so a temporary string object is constructed. All three can be used to provide a string value. For example:

    cout << get_global() << endl; 
cout << get_static() << endl;
cout << get_temp() << endl;

All three can provide a string that can be used to assign a string object. The important point is that the first two functions return along a lived object, but the third function returns a temporary object, but these objects can be used the same.

If these functions returned access to a large object, you would not want to pass the object to another function, so instead, in most cases, you'll want to pass the objects returned by these functions as references. For example:

    void use_string(string& rs);

The reference parameter prevents another copy of the string. However, this is just half of the story. The use_string function could manipulate the string. For example, the following function creates a new string from the parameter, but replaces the letters a, b, and o with an underscore (indicating the gaps in words without those letters, replicating what life would be like without donations of the blood types A, B, and O). A simple implementation would look like this:

    void use_string(string& rs) 
{
string s { rs };
for (size_t i = 0; i < s.length(); ++i)
{
if ('a' == s[i] || 'b' == s[i] || 'o' == s[i])
s[i] = '_';
}
cout << s << endl;
}

The string object has an index operator ([]), so you can treat it like an array of characters, both reading the values of characters and assigning values to character positions. The size of the string is obtained through the length function, which returns an unsigned int (typedef to size_t). Since the parameter is a reference, it means that any change to the string will be reflected in the string passed to the function. The intention of this code is to leave other variables intact, so it first makes a copy of the parameter. Then on the copy, the code iterates through all of the characters changing the a, b, and o characters to an underscore before printing out the result.

This code clearly has a copy overhead--creating the string, s, from the reference, rs; but this is necessary if we want to pass strings like those from get_global or get_static to this function because otherwise the changes would be made to the actual global and static variables.

However, the temporary string returned from get_temp is another situation. This temporary object only exists until the end of the statement that calls get_temp. Thus, it is possible to make changes to the variable knowing that it will affect nothing else. This means that you can use move semantics:

    void use_string(string&& s) 
{
for (size_t i = 0; i < s.length(); ++i)
{
if ('a' == s[i] || 'b' == s[i] || 'o' == s[i]) s[i] = '_';
}
cout << s << endl;
}

There are just two changes here. The first is that the parameter is identified as an rvalue reference using the && suffix to the type. The other change is that the changes are made on the object that the reference refers to because we know that it is a temporary and the changes will be discarded, so it will affect no other variables. Note that there are now two functions, overloads with the same name: one with an lvalue reference, and one with an rvalue reference. When you call this function, the compiler will call the right one according to the parameter passed to it:

    use_string(get_global()); // string&  version 
use_string(get_static()); // string& version
use_string(get_temp()); // string&& version
use_string("C string"); // string&& version
string str{"C++ string"};
use_string(str); // string& version

Recall that get_global and get_static return references to objects that will live the lifetime of the program, and for this reason the compiler chooses the use_string version that takes an lvalue reference. The changes are made on a temporary variable within the function, and this has a copy overhead. The get_temp returns a temporary object and so the compiler calls the overload of use_string that takes an rvalue reference. This function alters the object that the reference refers to, but this does not matter because the object will not last beyond the semicolon at the end of the line. The same can be said for calling use_string with a C-like string literal: the compiler will create a temporary string object and call the overload that has an rvalue reference parameter. In the final example in this code, a C++ string object is created on the stack and passed to use_string.

The compiler sees that this object is an lvalue and potentially can be altered, so it calls the overload that takes an lvalue reference that is implemented in a way that only alters a temporary local variable in the function.

This example shows that the C++ compiler will detect when a parameter is a temporary object and will call the overload with an rvalue reference. Typically, this facility is used when writing copy constructors (special functions used to create a new custom type from an existing instance) and assignment operators so that these functions can implement the lvalue reference overload to copy the data from the parameter, and the rvalue reference overload to move the data from the temporary to the new object. Other uses are for writing custom types that are move only, where they use resources that cannot be copied, for example file handles.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset