13
#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?
}
laike9m
  • 18,344
  • 20
  • 107
  • 140
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 1
    It works in the sense that `std::move(a)` produces a `const A &&` (remember that `std::move` merely casts its argument to a rvalue reference). But there's no such thing as a `push_back(const A &&)` -- only `push_back(const A &)` and `push_back(A &&)`--, and the first one will be called, causing a copy to happen (since you can't bind a const rvalue ref to a non-const rvalue ref, but you can bind it to a const lvalue reference). – peppe Aug 12 '16 at 11:45
  • What do you mean, "how is the copy avoided?". Are you asking how `vector::push_back(T &&)` is implemented? – Steve Jessop Aug 12 '16 at 11:53
  • @SteveJessop, in a way Yes. But I would fine not to go much in details. My basic question is that, since `A&& a` coming from outside might be created in some location. `std::vector` is contiguous container. How is it possible to avoid the copy at all? – iammilind Aug 12 '16 at 11:55
  • 3
    @iammilind: well, in the case where the value type `T` is the `struct A` from your code, moving that type is exactly the same as copying it, so the copy *isn't* avoided. For a type with a useful move constructor (like for example `vector` itself has) which does something faster than what the copy constructor does, then we'd be in business. But if a type stores all its data inside the object itself (i.e. no external data structure like `vector` has) then generally speaking you can't write a useful move constructor/assignment. – Steve Jessop Aug 12 '16 at 11:57
  • 4
    The thing to remember is that the concept Copyable is a subset of the concept Movable. Everything that is Copyable *automatically* is also Movable, even if the thing that C++ refers to as a move is actually implemented just as a copy. That's part of why moving doesn't specify what state the source is left in: it's permitted not to change its state at all. Whereas not everything that is Movable is also Copyable (for example `unique_ptr`), and for some Copyable things the move is different from the copy (like `vector`). – Steve Jessop Aug 12 '16 at 12:05
  • (I'm being a bit loose with terminology: actually there's MoveConstructible and MoveAssignable as separate concepts, rather than a single Movable concept, but the relationship with copying is the same for each) – Steve Jessop Aug 12 '16 at 12:09

2 Answers2

17

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.

JVApen
  • 11,008
  • 5
  • 31
  • 67
  • Why is it strange? The fact that `const T &` can bind to rvalues is not new in C++11. –  Aug 12 '16 at 11:50
  • Let me clearify that sentence, I meant the existance of `const A &&` – JVApen Aug 12 '16 at 11:50
  • 8
    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". – Steve Jessop Aug 12 '16 at 11:51
  • 1
    Okay, then still, why is it strange? `const`-qualified rvalues are not new in C++11 either. :) –  Aug 12 '16 at 11:51
  • 1
    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). – Miles Budnek Aug 12 '16 at 11:55
  • Indeed, as A is a POD, it will always be copied, unless in some sanitizing mode. – JVApen Aug 12 '16 at 12:01
  • 1
    @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. – Holt Aug 12 '16 at 12:07
  • 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. – laike9m Mar 15 '18 at 03:04
  • 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). – Spencer Aug 19 '19 at 14:48
3

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"
}
laike9m
  • 18,344
  • 20
  • 107
  • 140