How does Std::move work?

Asked

Viewed 517 times

10

I would like to understand the operation of std::move.

I realized that with the C++11 specification comes this new function, as well as now we have a new operator (&&).

What is the semantics and use of this new function and this new operator?

I read some explanations in English but I could not fully understand its functioning.

2 answers

10


C++ has two basic entities: values and types. It is not difficult to see that 3 is a value, which std::string is a guy, who decltype(itoa(sizeof(4))) is a guy and that double() is a value. The values in particular carry three characteristics: the first is the state, the information that this value has in memory. The second is the type, every value has a well defined type. The third, finally, is the category of value, which I will explain better soon.

Importantly, only the first is a run-time feature. The two exist merely during compilation and are discarded completely after the code has been fully generated. The value type manages how it will behave in run-time. The category of the value specifies how it should behave in relation to its life cycle. In other words, how to know when a value is no longer useful and can be safely destroyed.

The value categories are 5: rvalues, lvalues, xvalues, glvalues and prvalues.

But what’s relevant here is xvalue. A value is in this category when it is no longer useful and is expiring. For example:

void func(std::string str) { /* ... */ }

func(a);

The function func need to have a std::string. Note that the argument is passed by value, so having a reference is not enough, you need to have a std::string even. So, if a nay for a xvalue then a is still useful and needs to continue existing. So the only solution is to create a copy of a. But... if a for a xvalue, then I know that a is about to be destroyed as it is no longer useful. So it is possible move the data of a for str and leave a emptiness, after all a will soon be destroyed.

What happens is that in any context an implicit copy occurs, if the object is xvalue, then it will be moved and not copied. Well, now comes the motto to never be forgotten:

std::move does not move

That’s right, std::move does not perform the move operation. In reality, std::move performs no operation and generates zero code. std::move is a cast. Takes any value as argument and returns that same value in the category xvalue. And a value like T and in the category xvalue is denoted like this: T&&.

The operation of moving itself is executed by one of the constructors of the object to which it moves. This constructor takes as argument a xvalue, thus: Objeto(Objeto&& obj). An example:

Objeto b = std::move(a);

Here it is clear that all std::move makes is make a one xvalue for the correct constructor to be called. But it should be noted that a has not yet been destroyed, so the builder cannot leave a in an invalid state. Meaning that nothing prevents a be used in sequence.

Two good references I recommend:
The lecture "An Effective C++11/14 Sampler" by Scott Meyers
And the book of the same author: "Effective Modern C++"

3

There are several situations that either you keep doing specific manipulations to avoid data copying or you allow these copies to be made creating a performance cost.

This usually occurs in types where the value type semantics is desired (see more on the subject in that and in that answer, is another language but the idea is the same) that are normally stored in stack or as an integral part of another object in heap. In other words, you work effectively with the data value and not with a reference to the actual value. But to avoid the cost of copying these values some types are accessed by reference - through a pointer - maintaining their value semantics. String is a very typical case.

It is true that some types can be optimized by the compiler and this happened with string, for example. But the compiler does not know all types. It was necessary to allow each type to be defined in such a way that the optimization was always done.

With the std::move it is possible to move a value to another reference. This is done through a pointer, it is very cheap.

One might be wondering, why do you need to move? Why don’t you copy it like you always did in C++? The problem with copying is that it retains property of the object (value).

When you have value semantics, you never have two or more references to an object (a value) because it is self-referenced, the value exists by itself. When you copy the value, the copy is another object and has another owner (a variable, for example). Although initially the values are equal, they happen to be two completely different and independent objects.

You want to ensure that these types that have reference but use value semantics also do not have more than one owner. A simple copy would create a new reference for the object. This has implications in concurrent execution environments and would complicate automatic memory management because it would have to control how many references the object has and only when it has zero should the object be destroyed.

As the possibility to move, we are indicating to the compiler that the object can only have one owner, which is not like two owners to try to access the object simultaneously and does not need control how many owners have. This greatly simplifies everything that has to be done in your code and what the compiler has to generate to control the lifetime of the object.

It has always been possible to do this, but now it has a standardized form and that the compiler can benefit since it is standard. He can make decisions based on this.

In practice this std:move disappears from code after compiled. It does serve to inform how the compiler should handle it.

This made it possible to create the std:unique_ptr which greatly simplified automatic memory management without implicating in overhead processing or memory. And this is a revolution for C++.

Its implementation is very simple, it’s like this:

template <class T>
typename remove_reference<T>::type&&
move(T&& a) {
    return a;
}

Example of use:

template <class T> swap(T& a, T& b) {
    T tmp(a); //a passa ter duas referências p/ "a", a original que passou o parâmetro e aqui
    a = b;//e agora duas cópias para "b"
    b = tmp; //mais um cópia para "tmp" que já é cópia para "a"
}

See the difference:

template <class T> swap(T& a, T& b) {
    T tmp(std::move(a)); //nenhuma cópia é feita em nenhum do casos, só ponteiros se movimentam
    a = std::move(b);   
    b = std::move(tmp);
}

I put in the Github for future reference.

When you use the std:move the variable is giving up the property of the object. Which can be given back by the new owner.

Surely there is much more to talk about. And all I have said is a simplification.

Reference in Wikipedia.

Pre-official documentation.

  • I think I’m beginning to understand. But what would happen if I did something like: b = Std::move(a); a->foo(); This would cause a fatal error?

  • No. This is exactly why you have the move. The compiler knows what to do in such cases. All this semantics is just to make the compiler aware of what it can and cannot do. In this case a and tmp are the same thing. Both work. Essentially the move does not change the way to do anything, changes the internal way the compiler handles it. This is just extra information for the compiler to handle your data better. Perhaps seeing is easier to understand. Look what I did: http://ideone.com/L3t9Hb

Browser other questions tagged

You are not signed in. Login or sign up in order to post.