46

I have heard Scott Meyers say "std::move() doesn't move anything" ... but I haven't understood what it means.

So to specify my question consider the following:

class Box { /* things... */ };

Box box1 = some_value;
Box box2 = box1;    // value of box1 is copied to box2 ... ok

What about:

Box box3 = std::move(box1);

I do understand the rules of lvalue and rvalue but what I don't understand is what is actually happening in the memory? Is it just copying the value in some different way, sharing an address or what? More specifically: what makes moving faster than copying?

I just feel that understanding this would make everything clear to me. Thanks in advance!

EDIT: Please note that I'm not asking about the std::move() implementation or any syntactic stuff.

jotik
  • 14,982
  • 9
  • 48
  • 106
Laith
  • 1,037
  • 2
  • 9
  • 16
  • 3
    Have a read of http://stackoverflow.com/questions/21358432/why-is-stdmove-named-stdmove – Richard Critten Apr 24 '16 at 19:36
  • 7
    It's faster because moving allows the source to be left in a invalid state, so you can steal it's resources. For example, if a object holds a pointer to a large block of allocated memory, a move can simply steal the pointer while a copy must allocate its own memory and copy the whole memory block. – Unimportant Apr 24 '16 at 19:38
  • 2
    Copying an object means you have to copy it's contents in memory. Let's assume you have a vector containing 2Gb of data. If you copy the vector, these 2Gb have to be copied in memory, which takes time. Moving means the data in memory stays as it is. Only the reference to this data is moved from the old object to the object you are moving to. – bweber Apr 24 '16 at 19:40
  • @ user1488118 I did read that somewhere and it made sense to me until I read about when vector is full it reallocate new memory and the objects in the old vector will be moved to the new allocated memory ... that just messed my understanding ... – Laith Apr 24 '16 at 19:44
  • Re. "std::move doesn't move anything" - he is saying that `std::move(box1);` does not move anything; however `Box b = std::move(box1);` *does* move something. The distinction is that the movement is enacted by the initialization of `b`, not by the call of `std::move`. – M.M May 26 '16 at 11:05

3 Answers3

65

As @gudok answered before, everything is in the implementation... Then a bit is in user code.

The implementation

Let's assume we're talking about the copy-constructor to assign a value to the current class.

The implementation you'll provide will take into account two cases:

  1. the parameter is a l-value, so you can't touch it, by definition
  2. the parameter is a r-value, so, implicitly, the temporary won't live much longer beyond you using it, so, instead of copying its content, you could steal its content

Both are implemented using an overload:

Box::Box(const Box & other)
{
   // copy the contents of other
}

Box::Box(Box && other)
{
   // steal the contents of other
}

The implementation for light classes

Let's say your class contains two integers: You can't steal those because they are plain raw values. The only thing that would seem like stealing would be to copy the values, then set the original to zero, or something like that... Which makes no sense for simple integers. Why do that extra work?

So for light value classes, actually offering two specific implementations, one for l-value, and one for r-values, makes no sense.

Offering only the l-value implementation will be more than enough.

The implementation for heavier classes

But in the case of some heavy classes (i.e. std::string, std::map, etc.), copying implies potentially a cost, usually in allocations. So, ideally, you want to avoid it as much as possible. This is where stealing the data from temporaries becomes interesting.

Assume your Box contains a raw pointer to a HeavyResource that is costly to copy. The code becomes:

Box::Box(const Box & other)
{
   this->p = new HeavyResource(*(other.p)) ; // costly copying
}

Box::Box(Box && other)
{
   this->p = other.p ; // trivial stealing, part 1
   other.p = nullptr ; // trivial stealing, part 2
}

It's plain one constructor (the copy-constructor, needing an allocation) is much slower than another (the move-constructor, needing only assignments of raw pointers).

When is it safe to "steal"?

The thing is: By default, the compiler will invoke the "fast code" only when the parameter is a temporary (it's a bit more subtle, but bear with me...).

Why?

Because the compiler can guarantee you can steal from some object without any problem only if that object is a temporary (or will be destroyed soon after anyway). For the other objects, stealing means you suddenly have an object that is valid, but in an unspecified state, which could be still used further down in the code. Possibly leading to crashes or bugs:

Box box3 = static_cast<Box &&>(box1); // calls the "stealing" constructor
box1.doSomething();         // Oops! You are using an "empty" object!

But sometimes, you want the performance. So, how do you do it?

The user code

As you wrote:

Box box1 = some_value;
Box box2 = box1;            // value of box1 is copied to box2 ... ok
Box box3 = std::move(box1); // ???

What happens for box2 is that, as box1 is a l-value, the first, "slow" copy-constructor is invoked. This is the normal, C++98 code.

Now, for box3, something funny happens: The std::move does return the same box1, but as a r-value reference, instead of a l-value. So the line:

Box box3 = ...

... will NOT invoke copy-constructor on box1.

It will invoke INSTEAD the stealing constructor (officially known as the move-constructor) on box1.

And as your implementation of the move constructor for Box does "steal" the content of box1, at the end of the expression, box1 is in a valid but unspecified state (usually, it will be empty), and box3 contains the (previous) content of box1.

What about the valid but unspecified state of a moved-out class?

Of course, writing std::move on a l-value means you make a promise you won't use that l-value again. Or you will do it, very, very carefully.

Quoting the C++17 Standard Draft (C++11 was: 17.6.5.15):

20.5.5.15 Moved-from state of library types [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

This was about the types in the standard library, but this is something you should follow for your own code.

What it means is that the moved-out value could now hold any value, from being empty, zero, or some random value. E.g. for all you know, your string "Hello" would become an empty string "", or become "Hell", or even "Goodbye", if the implementer feels it is the right solution. It still must be a valid string, though, with all its invariants respected.

So, in the end, unless the implementer (of a type) explicitly committed to a specific behavior after a move, you should act as if you know nothing about a moved-out value (of that type).

Conclusion

As said above, the std::move does nothing. It only tells the compiler: "You see that l-value? please consider it a r-value, just for a second".

So, in:

Box box3 = std::move(box1); // ???

... the user code (i.e. the std::move) tells the compiler the parameter can be considered as a r-value for this expression, and thus, the move constructor will be called.

For the code author (and the code reviewer), the code actually tells it is ok to steal the content of box1, to move it into box3. The code author will then have to make sure box1 is not used anymore (or used very very carefully). It is their responsibility.

But in the end, it is the implementation of the move constructor that will make a difference, mostly in performance: If the move constructor actually steals the content of the r-value, then you will see a difference. If it does anything else, then the author lied about it, but this is another problem...

paercebal
  • 75,594
  • 37
  • 124
  • 157
  • Good explaining but to be honest I already understand most of what you explained ... but what confused me , that POD cannot be moved but copied (not sure 100%) and as I commented above I also heard Scott Meyers talk about moving (NOT copying) elements of vector when it reallocate so I was like how could we move elements? moving the whole vector I understand it just swapping pointers but moving elements that didnt make sense to me ... +1 anyway – Laith May 26 '16 at 08:09
  • 2
    @Leo : I completed the answer to explain when moving was made no sense for some types (mainly, simple classes containing only raw integers). For your vector question, when the vector will try to move each individual object, if the move implementation isn't available for that object, then the move attempt will result in a simple copy instead. – paercebal May 26 '16 at 10:41
  • @hyde : I removed those typos (from the original "operator =" based code, before it was changed into constructors). Thanks! :-) – paercebal Jul 26 '16 at 21:53
  • @paercebal: Explanation you provide in your answers is always superb. I really like to read the answers written by you. well written answer !!! – Destructor Mar 07 '18 at 06:46
  • @Destructor Thanks for the comment. It did prompt me to revisit the answer, and clarify its "empty state" thingy, so might want to re-read it (the section "What about the valid but unspecified state of a moved-out class?", that is) – paercebal Mar 08 '18 at 09:49
  • Is it UB to access the content of moved out variable ? – Destructor Mar 08 '18 at 09:52
  • @Destructor : No. The content of the variable is valid (i.e, you can use it), but unspecified (i.e. you know nothing about its content, so you should look at it before using it). It's a bit like a function with a string parameter: Inside the body of the function, you know nothing about the content of the string. It could be an XML string, a JSON string, a simple message, or the password for your GOG account, or it could be empty. Inside that function you can use that string. You just don't know what's inside without looking at it first. – paercebal Mar 08 '18 at 09:59
17

It's all about implementation. Consider simple string class:

class my_string {
  char* ptr;
  size_t capacity;
  size_t length;
};

Semantics of copy requires us to make a full copy of string including allocation of another array in dynamic memory and copying *ptr contents there, which is expensive.

Semantics of move requires us only to transfer the value of pointer itself to new object without duplicating contents of string.

If, of course, class doesn't use dynamic memory or system resources, then there is no difference between moving and copying in terms of performance.

gudok
  • 3,584
  • 2
  • 16
  • 27
  • 1
    This does make sense but I read about when vector is full it reallocate new memory and the objects in the old vector will be moved (NOT copied) to the new allocated memory ... which confused me ... – Laith Apr 24 '16 at 19:55
  • @WLION: what's confusing about that? I can't imagine any scenario in which copy is cheaper than move. – GingerPlusPlus Apr 24 '16 at 19:57
  • @WLION The vector itself is in some sense a collection of pointers to objects, and while the vector storing the pointers may have to be copied to a new area, those pointers themselves don't have to change. They still point to the same objects stored in the same original space they were. – Havenard Apr 24 '16 at 19:58
  • @Havenard I did test that in visual studio for example a vector of size 2 and capacity 2 then I push_back with std::move() to make the vector to reallocate ... when I access the vector elements I dont have to dereference them to get the values of the objects – Laith Apr 24 '16 at 20:04
  • 1
    @Havenard a vector is not a collection of pointers. The C++ standard defines it to be a contiguous block of memory, containing the objects, much like an array. The moving of the objects themselves is done to eliminate the performance penalty of a deep copy, on objects that support move semantics. If no move is possible, then it simply copies the objects. – Elkvis May 25 '16 at 17:20
1

The std::move() function should be understood as a cast to the corresponding rvalue type, that enables moving the object instead of copying.


It might make no difference at all:

std::cout << std::move(std::string("Hello, world!")) << std::endl;

Here, the string was already an rvalue, so std::move() didn't change anything.


It might enable moving, but may still result in a copy:

auto a = 42;
auto b = std::move(a);

There's no more efficient way of creating an integer that simply copying it.


Where it will cause a move to happen is when the argument

  1. is an lvalue, or lvalue reference,
  2. has a move constructor or move assignment operator, and
  3. is (implicitly or explicitly) the source of a construction or assignment.

Even in this case, it's not the move() itself that actually moves the data, it's the construction or assignment. std:move() is simply the cast that allows that to happen, even if you have an lvalue to start with. And the move can happen without std::move if you start with an rvalue. I think that's the meaning behind Meyers's statement.

Toby Speight
  • 23,550
  • 47
  • 57
  • 84
  • copying is bad because of memory occupied ? and move is bad because of time needed for deleting de unnecessary object? Is what I said somehow correct? – Cătălina Sîrbu Apr 17 '20 at 19:12