Move Constructor Observations

Although using an rvalue reference enables move semantics, it doesn’t magically make it happen. There are two steps to enablement. The first step is that the rvalue reference allows the compiler to identify when move semantics can be used:

Useless two = one;          // matches Useless::Useless(const Useless &)
Useless four (one + three); // matches Useless::Useless(Useless &&)

The object one is an lvalue, so it matches the lvalue reference, and the expression one + three is an rvalue, so it matches the rvalue reference. Thus, the rvalue reference directs initialization for object four to the move constructor. The second step in enabling move semantics is coding the move constructor so that it provides the behavior we want.

In short, the presence of one constructor with an lvalue reference and a second constructor with an rvalue reference sorts possible initializations into two groups. Objects initialized with an lvalue object use the copy constructor, and objects initialized with an rvalue object use the move constructor. The code writer then can endow these constructors with different behaviors.

This raises the question of what happened before rvalue references were part of the language. If there is no move constructor and the compiler doesn’t optimize away the need for the copy constructor, what should happen? Under C++98, the following statement would invoke the copy constructor:

Useless four (one + three);

But an lvalue reference doesn’t bind to an rvalue. So what happens? As you may recall from Chapter 8, a const reference parameter will bind to a temporary variable or object if the actual argument is an rvalue:

int twice(const & rx) {return 2 * rx;}
...
int main()
{
    int m = 6;
    // below, rx refers to m
    int n = twice(m);
    // below, rx refers to a temporary variable initialized to 21
    int k = twice(21);
...

So in the Useless case, the formal parameter f will be initialized to a temporary object that’s initialized to the return value of operator+(). Here is an excerpt from the result of using an older compiler with Listing 18.2 and omitting the move constructor:

...
Entering operator+()
int constructor called; number of objects: 4
Number of elements: 30 Data address: 01C337C4
temp object:
Leaving operator+()
copy const called; number of objects: 5
Number of elements: 30 Data address: 01C337E8
destructor called; objects left: 4
deleted object:
Number of elements: 30 Data address: 01C337C4
copy const called; number of objects: 5
Number of elements: 30 Data address: 01C337C4
destructor called; objects left: 4
deleted object:
Number of elements: 30 Data address: 01C337E8
...

First, within the Useless::operator+() method, a constructor creates temp, allocating storage for 30 elements at location 01C337C4. Then the copy constructor creates a temporary copy to which f will refer, copying the information to location 01C337E8. Next, temp, which uses location 01C337C4, gets deleted. Then a new object, four, is constructed, reusing the recently freed memory at 01C337C4. Then the temporary argument object, which used location 01C337E8, gets deleted. So three complete objects were constructed, and two of them were destroyed. This is the sort of extra work that move semantics are meant to eliminate.

As the g++ example shows, an optimizing compiler might eliminate extra copying on its own, but using an rvalue reference lets the programmer dictate move semantics when appropriate.

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

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