15

I am reading about the std::move, move constructor and move assignment operator. To be honest, all I got now is confusion. Now I have a class:

class A{
  public:
    int key;
    int value;
    A(){key = 3; value = 4;}
    //Simple move constructor
    A(A&& B){ A.key = std::move(B.key); 
              A.value = std::move(B.value);}
};
  1. I thought B is an rvalue reference, why you can apply std::move to an ravlue reference's member?
  2. After B.key and B.value have been moved, both have been invalidated, but how B as an object of class A gets invalidated?
  3. What if I have A a(A()), A() is apparently an rvlaue, can A() be moved by std::move and why?
  4. Similarly, if I have a function

    int add(int && z){ int x = std:move(z); int y = std:move(z); return x+y; }

What if I call add(5), how can 5 be moved and why? And notice that z has been moved twice, after z has been moved first time, it has been invalidated, how can you move it again?

  1. When defining foo (T && Z )(T, Z can be anything), in the body of the definition Why on earth I should use std::move(Z) since Z is already passed by an rvalue reference and when should I use std::move?
Allanqunzi
  • 3,230
  • 1
  • 26
  • 58

2 Answers2

16

std::move does not move anything, but "marks" its argument to be a rvalue reference. Technically, it converts the type to a rvalue reference. Then, the rvalue reference it's being moved by the corresponding move constructor or move assignment operator. For objects that contain only members with trivial move ctors/assignment operators, the move ctor/assignment operator is trivial and simply copies. In general, the move ctor/assignment operator of the object calls the move ctor/assignment operator of all its members.

So, whenever you write

int x = 10;
int y = std::move(x); 

on the right hand side of the assignment y = std::move(x), you have a rvalue reference of type int&&. However, int does not have a non-trivial move ctor, and the rvalue is simply copied into y, nothing is changed in x.

On the other hand,

string s = "some string";
string moved_s = std::move(s); // here we tell the compiler that we can "steal" the resource of s

is different. The move constructor of moved_s kicks in, and "steals" (i.e. swaps internal pointers etc) the resource of s, because the latter is a rvalue reference. At the end, s will not contain any element.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 1
    The implicitly-generated move constructor/assignment operator will move (not copy) - this distinction matters if we're talking about a class containing a string member variable, for example – M.M Dec 16 '14 at 05:16
  • Can you elaborate on item 5? E.g., if I have `A b;` can I do `A a(b);` and why? – Allanqunzi Dec 16 '14 at 07:04
  • @user2345484 regarding item 5, no you cannot, you need to use `move` whenever you passing anything but a rvalue to your function. If you try passing a lvalue it won't compile. In your example just above this comment, you CAN do this since most probably `A` has a default copy ctor which will copy. To force the move, convert `b` to a rvalue reference via `std::move(b)`, then the move ctor of `a` will kick in. – vsoftco Dec 16 '14 at 07:12
  • @user2345484 The name `std::move` is very unfortunate in my opinion, it should have been named something like `lvalue` or `decay_lvalue` or anything but not `move`, as it gives the false impression that the function "performs" some non-trivial action. If you look at the definition of `std::move` http://en.cppreference.com/w/cpp/utility/move you'll see it uses the wrapper function `std::remove_reference`. Take the look at the latter to understand how it works. – vsoftco Dec 16 '14 at 07:20
  • For example in the function of item 4, I just don't get it why you should do `std::move(z)` and also the same for `A a(A())`? Since `z` is already passed by as an rvalue reference and `std::move(z)` also returns an rvalue reference. – Allanqunzi Dec 16 '14 at 07:20
  • @user2345484 you don't have to use `std::move`. This is a bit tricky, the parameter `z` is a lvalue of type rvalue reference. It is a lvalue since it has a name and can take its address, and also can put it on the left hand side of the assignment operator. Anyway you have an `int`, which is not moved at all in your expressions. It's better to look at some concrete examples of how `move` is used in real code to understand what's going on. If instead you had a `string`, then `string x = z` will copy, and `string x = std::move(z)` will move. – vsoftco Dec 16 '14 at 07:28
  • @user2345484 regarding item 3, you don't need to use `std::move(A())`, since `A()` is a rvalue (a nameless temporary). It is not the same as `std::move(z)`, where in the latter case `z` is a named parameter. – vsoftco Dec 16 '14 at 07:30
  • @user2345484 you should try reading Scott Meyers "Effective Modern C++", he goes into a lot of detail about moving/lvalue/rvalue references etc. The thing to remember is that all named parameters are lvalues, but can have rvalue types. Whenever something has a name, it is most probably a lvalue, and will be treated like a lvalue in an assignment. Here is also a great explanation: http://stackoverflow.com/a/7518365/3093378 – vsoftco Dec 16 '14 at 07:32
  • thank you very much. I will read that. I also watched a video about universal reference by him. I think I somehow got it. But in item 3, `B` is also an named parameter. – Allanqunzi Dec 16 '14 at 07:37
  • @user2345484 what I meant is that when you write `A a = A();`, there is no need to write it as `A a = std::move(A());` However, if you have `A a = z`, most probably you need `A a = std::move(z);` to enable moving of `z`, even if `z` was passed to a function `foo(A&&)`. `z` itself is a lvalue (referring to a temporary) and it won't be moved but copied, so in fact you end up copying a temporary if you don't explicitly `std::move` it. The universal reference is a great video, but it's hard to understand it at first, at least I had to watch it multiple times. – vsoftco Dec 16 '14 at 07:40
  • @user2345484, have a look at this short article: http://www.artima.com/cppsource/rvalue.html Make sure you get these basics right before advancing to more complex topics (e.g. universal references). – Roman L Dec 16 '14 at 16:55
  • @RomanL, I think in that link there is mistake in the code `A a; A&& a_ref2 = a; // an rvalue reference`, here `a` is a lvalue, you can not declare an rvalue reference to a lvalue. – Allanqunzi Dec 16 '14 at 18:21
  • @user2345484 Yes, I am not sure why they picked this example. A better option would be something like `A&& a_ref2 = A();`. But otherwise the article provides a good explanation IMO. – Roman L Dec 16 '14 at 19:02
1
  1. B is the name of an object. Once a reference has been bound, it names an object. The distinction "rvalue reference", "lvalue reference" and "named object" only applies to how the name can be bound before you got this far.

B.key is the name of a variable in the object which was supplied as argument to this function call.

  1. "invalidated" is not part of the standard terminology for moving. Standard library objects are left in an unspecified state after being moved out of; but that's not what's going on here.

The line A.key = std::move(B.key) invokes the built-in definition of assignment for an int (this is a simple assignment, not a function call), which is just a copy. So B.key retains its value.

  1. For A(B()) to compile, B must be a typename which you haven't defined yet. (Did you mean A(A()) ? If so, then the answer is "Yes").

  2. See 2

  3. Use std::move(Z.foo) whenever you want to move out of Z.foo instead of copying from Z.foo.

M.M
  • 138,810
  • 21
  • 208
  • 365