How does std :: move work?

10

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

I noticed that with the C ++ 11 specification this new function arises, and 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 how it works.

    
asked by anonymous 03.02.2015 / 16:04

2 answers

9

C ++ has two basic entities: values and types. It is not difficult to see that 3 is a value, that std::string is a type, that decltype(itoa(sizeof(4))) is a type and that double() is a value. 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.

Note that only the first is a run-time feature. The two exist merely during compilation and are discarded completely after the code has been completely generated. Value type manages how it will behave at run-time. The value category specifies how it should behave in relation to its life cycle. In other words, how do you 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 matters here is xvalue . A value is in this category when it is no longer useful and is epipering. For example:

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

func(a);

The function func must have a std::string . Notice that the argument is passed by value, so having a reference is not enough, you must have std::string same. So, if a not is a xvalue then a is still useful and needs to continue to exist. So the only solution is to create a copy of a . But ... if a is xvalue , then I know a is about to be destroyed because it is no longer useful. So you can move data from a to str and leave a empty, after a will be destroyed soon.

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 fact, std::move does not perform any operation and generates zero code. std::move is a cast. It takes any value as argument and returns that same value in the xvalue category. And a value of type T and category xvalue is denoted thus: T&& .

The move operation itself is performed by one of the constructors of the object to which it moves. This constructor takes as an argument a xvalue , like this: Objeto(Objeto&& obj) . An example:

Objeto b = std::move(a);

Here it is clear that all that std::move does is make a a xvalue so that the correct constructor is called. But it should be noted that a has not yet been destroyed, so the constructor can not leave a in an invalid state. Meaning that nothing prevents a from being used in sequence.

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

    
03.02.2015 / 17:06
2

There are a number of situations that either make you specific manipulations to avoid copying data or allow these copies to be made creating a cost of performance.

This typically occurs in types where you want value type semantics (see more on this topic this > and this answer, it's another language but the idea is the same) that are usually stored in stack or as an integral part of another object in the heap . In other words, you work effectively with the value of the data 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 - while maintaining their value semantics. String is a very typical case.

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

With std::move you can move one value to another reference. This is done through a pointer, it is very cheap.

Someone may be wondering, why do you need to move? Why not copy as always in C ++? The problem with copying is that it retains property of the object (of the 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 the same, 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 the automatic management of memory because it would have to control how many references the object has and only when it has zero is that the object should be destroyed.

As the possibility of moving, we are telling the compiler that the object can only have one owner, who does not have as two owners trying to access the object simultaneously and does not need control how many owner has . 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.

You've always been able to do this, but now it has a standardized form and the compiler can benefit since it is standard. He can make decisions based on this.

In practice this std:move disappears from the code after compiling. It even serves to inform how the compiler should deal with it.

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

The implementation of it is very simple, it's something like this:

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

Example usage:

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);
}

When you use std:move , the variable is giving up the object's property. That can be given back by the new owner.

Surely there is more to talk about. And everything I said is a simplification.

Wikipedia Reference .

Pre-official documentation .

    
03.02.2015 / 16:49