Does std::move work with lvalue references? How does std::move work on standard containers?

18,488

Solution 1

std::move doesn't do a move. It actually casts the lvalue reference to an rvalue reference. In this case, the result of the move is a const A && (which is totally useless by the way).

std::vector has an overload for a const A & and a A &&, so the overload with const A & will get chosen and the const A && is implicitly casted to const A &

The fact that std::move can be called on const objects, is strange/unexpected behavior for most programmers, though it somehow is allowed. (Most likely they had a use case of it, or none to prevent it)

More specific for your example, the move constructor of the class A will get called. As A is a POD, this most likely will just do a copy as all bits just have to move/copied to the new instance of A.

As the standard only specifies that the original object has to be in a valid though unspecified state, your compiler can keep the bits in A in place and doesn't have to reset them all to 0. Actually, most compilers will keep these bits in place, as changing them requires extra instructions, which is bad for performance.

Solution 2

Created a snippet to show it. Though in your example default constructor will be called, but you get the idea.

#include <vector>
#include <iostream>

struct A { 
  int a[100];
  A() {}
  A(const A& other) {
    std::cout << "copy" << std::endl;
  }
  A(A&& other) {
    std::cout << "move" << std::endl;
  }
};

void foo(const A& a) {
  std::vector<A> vA; 
  vA.push_back(std::move(a));
}

void bar(A&& a) {
  std::vector<A> vA; 
  vA.push_back(std::move(a));
}

int main () {
  A a;
  foo(a);            // "copy"
  bar(std::move(a)); // "move"
}
Share:
18,488
iammilind
Author by

iammilind

"Among programming languages, I am C++ ..." — BG 10.19...

Updated on July 18, 2022

Comments

  • iammilind
    iammilind almost 2 years
    #include <vector>
    
    struct A { int a[100]; };
    
    void foo (const A& a) {
      std::vector<A> vA; 
      vA.push_back(std::move(a));  // how does move really happen?
    }
    
    int main () {
      A a;
      foo(a);
    }
    

    The above code compiles fine. Now everywhere it's written that move avoids copying.
    Following are my queries:

    1. Does the move really work when one deals with a lvalue [non]-const reference?
    2. Even with "rvalue reference", how is the copy avoided when the object is inserted into a standard container like above?

    e.g.

    void foo (A&& a) {  // suppose we invoke this version
      std::vector<A> vA; 
      vA.push_back(std::move(a));  // how copy is avoided?
    }
    
  • Admin
    Admin over 7 years
    Why is it strange? The fact that const T & can bind to rvalues is not new in C++11.
  • JVApen
    JVApen over 7 years
    Let me clearify that sentence, I meant the existance of const A &&
  • Steve Jessop
    Steve Jessop over 7 years
    It stops being strange when you consider that in C++, adding std::move into a bit of code that otherwise would result in a copy, and in general the Movable concept mean "move if possible otherwise copy". They don't mean "move if possible otherwise fail to compile".
  • Admin
    Admin over 7 years
    Okay, then still, why is it strange? const-qualified rvalues are not new in C++11 either. :)
  • Miles Budnek
    Miles Budnek over 7 years
    No copy is avoided. Your two foo functions will behave exactly the same since you've not defined a move constructor (nor does your A class have any movable resources).
  • JVApen
    JVApen over 7 years
    Indeed, as A is a POD, it will always be copied, unless in some sanitizing mode.
  • Holt
    Holt over 7 years
    @MilesBudnek Move constructor is implicitly defined in this case, and while it is true that in the end you will have to do a copy of A::a because an array is not movable, the first version of foo will call push_back(A const&) while the second will call push_back(A&&), so these functions do not behave exactly the same.
  • Steve Jessop
    Steve Jessop over 7 years
    You could probably even make the difference into observable behaviour if you specialised std::allocator<A> to detect what construct overload the vector calls. That's the sense in which it "is" a move even though it actually does the same as copy for A.
  • laike9m
    laike9m about 6 years
    I think you should make it more clear in "More specific for your example, the move constructor of the class A will get called.". Like you said, the two examples behave differently, so only in the second example the move constructor gets called.
  • Slav
    Slav almost 5 years
    it would be more demonstrative if you write foo(std::move(a));
  • Spencer
    Spencer over 4 years
    OK, so what if there's a non-copyable object with a move constructor but no copy constructor? Will it select the move constructor? (yeah, ideally the copy constructor will be deleted).