Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 move and reassigning

Tags:

c++

c++11

This code does compile but I'm just beginning studying C++11 and I can't understand what's happening here behind the scenes.

void func(int&& var)
{
    int var2(var);
}

void main()
{

    int var1 = 22;
    func(move(var1));
}

My guess: move(var1) returns a r-value of var1 (probably its data) and the func function is initializing var2 with the r-value of var1. But why is var1 still valid after the func call? Shouldn't it have an invalid value because its temp value has been re-assigned to var2?

like image 288
Johnny Pauling Avatar asked Dec 12 '22 16:12

Johnny Pauling


1 Answers

There are several issues here.

One is that you're working with an int. And for an int a copy is just as fast as a move. So it's perfectly reasonable for a move to be implemented as a copy.

There is no requirement that move construction (or assignment) alter the value of the thing being moved from. It's a logical error in your code to continue to treat it as if it had a useful value, but it's not required that the value become useless.

The second is that your code as written doesn't actually move anything. Simply using ::std::move does not result in something being moved. It's just a nice way to turn an lvalue into an rvalue so that something can be moved. In your case, var has a name, so it's an lvalue. Your initialization of var2 in func is actually a copy, not a move. If you had written it int var2(move(var)); it would then be a move and var1 in main would be potentially invalidated at that point.

To reiterate, the ::std::move function is just there to signal to people reading your code that a move may happen and that the value of the variable after that cannot be counted on. It doesn't actually move anything.

Here is a marked up version of your code:

// func requires a temporary argument, something that can be moved from
void func(int&& var)
{
    int var2(var); // Doesn't actually call int(int &&), calls int(int &)
    // int var2(move(var));  // Would actually call int(int &&) and a move would
                             // happen then at this point and potentially alter the
                             // value of what var is referencing (which is var1
                             // in this code).
}

void main()
{
    int var1 = 22;
    func(move(var1)); // Tell people that var1's value may be unspecified after
                      // Also, turn var1 into a temporary to satisfy func's
                      // signature.
}

Since your code, as written, does not result in any moves happening, here is a version that does definitely move something somewhere:

#include <vector>
#include <utility>

using ::std;

// Still require an rvalue (aka a temporary) argument.
void func(vector<int>&& var)
{
   // But, of course, inside the function var has a name, and is thus an lvalue.
   // So use ::std::move again to turn it into an rvalue.
   vector<int> var2(move(var));
   // Poof, now whetever var was referencing no longer has the value it used to.
   // Whatever value it had has been moved into var2.
}

int main()
{
   vector<int> var1 = { 32, 23, 66, 12 };
   func(move(var1)); // After this call, var1's value may no longer be useful.
   // And, in fact, with this code, it will likely end up being an empty
   // vector<int>.
}

And, of course, this way of writing it is silly. There are sometimes reasons to specify that an argument be a temporary. And that's usually if you have one version of a function that takes a reference, and another that takes an rvalue reference. But generally you don't do that. So, here's the idiomatic way to write this code:

#include <vector>
#include <utility>

using ::std;

// You just want a local variable that you can mess with without altering the
// value in the caller. If the caller wants to move, that should be the caller's
// choice, not yours.
void func(vector<int> var)
{
   // You could create var2 here and move var into it, but since var is a
   // local variable already, why bother?
}

int main()
{
   vector<int> var1 = { 32, 23, 66, 12 };
   func(move(var1));  // The move now does actually happen right here when
                      // constructing the argument to func and var1's value
                      // will change.
}

Of course, giving a name to var1 in that code is kind of silly. Really, it should just be written this way:

   func(vector<int>{ {32, 23, 66, 12} });

Then you're just constructing a temporary vector right there and passing it into func. No explicit use of move required.

like image 73
Omnifarious Avatar answered Dec 30 '22 01:12

Omnifarious