What does "moving contents" in the use of std::move() mean?

Below is example code from [1].

1
2
3
4
5
6
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;

myvector.push_back(foo);                    // copies
myvector.push_back(std::move(bar));         // moves 


There is a remark:

The second call moves the value of bar into the vector. This transfers its content into the vector (while bar loses its value, and now is in a valid but unspecified state).


It's unclear to me what is exactly being transferred. I know std::move() doesn't actually "move" anything [2, @55:10],[3] but underneath the hood, something is done with the rvalue that is to be moved.

In the example above, I imagine that "moving content" means that the memory region allocated for bar, containing "bar-string", is somehow linked (as opposed to being copied) to a location in myvector.

Consider a different example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <string>

struct A
{
    std::string _name;
    A(std::string&& name) : _name(std::move(name)) {
        // Initializer list has moved contents of name into this->_name
    }
};

int main()
{
    A p1("Alice");
}


Here, I imagine that there's some memory region containing "Alice" (except this memory region isn't accessible for storage; effectively "temporary"). Because A defines a move-ctor in which std::move() is used, instead of copying "Alice" from that memory region into p1._name, p1._name simply points to the previous memory region.

[1] http://www.cplusplus.com/reference/utility/move/
[2] https://www.youtube.com/watch?v=ZG59Bqo7qX4
[3] https://stackoverflow.com/a/27026280/5972766
Last edited on
std::move is possibly one of the most mis-understood in C++. It doesn't move and doesn't generate any code. It simply applies casts to produce a ref ref (&& or r-value) type if possible. If a function (standalone or member) offers a && type option, then this version will be used when choosing which version of the function to use.

What the && version of the function does is down to the function. Usually it is only used when the object uses dynamic memory. If the object doesn't use dynamic memory then normally there's no benefit in using std::move. Simply, an r-value type means that the object is a temporary and is going to be destroyed.

When used with dynamic memory, there is an area of memory containing the data and a pointer variable holding that address. Rather than copying this memory to another memory area (probably allocated by the container), all that happens is that the value of the pointers (ie the memory addresses) are swapped - no copying. The original memory address is set in the new object and the old memory address is set in the temporary object that is going to be destroyed as its temporary (rvalue). The destructor of this temp object will delete the old memory and hey-presto the object has been 'moved' without copying its contents - hence giving a better performance. It's this 'memory swap' that means that std::move() only has benefit when used with dynamic memory. If used with stack memory (eg c-style array), it does nothing. The memory addresses can't be swapped and a copy still happens.
std::move is possibly one of the most mis-understood in C++. It doesn't move and doesn't generate any code. It simply applies casts to produce a ref ref (&& or r-value) type if possible. If a function (standalone or member) offers a && type option, then this version will be used when choosing which version of the function to use.


Understood.

1
2
3
4
5
6
7
8
9
10
11
12
int&& Incr(int&& temp)
{
    // Inside this func, temp is treated as lvalue
    std::cout << "addr:" << &temp << " val:" << temp << "\n";
    temp++;  // Fixed typo; previously temp = ++temp;
    return std::move(temp); // "return temp" causes a compiler error
}

int main()
{
    std::cout << "Outside:" << Incr(10); 
}



What the && version of the function does is down to the function ... Rather than copying this memory to another memory area (probably allocated by the container), all that happens is that the value of the pointers (ie the memory addresses) are swapped - no copying. The original memory address is set in the new object and the old memory address is set in the temporary object that is going to be destroyed as its temporary (rvalue). The destructor of this temp object will delete the old memory and hey-presto the object has been 'moved' without copying its contents


So Function(std::move(value)) attempts to convert value into an r-value so that the correct overload of Function that takes an r-value reference will execute. What Function does is user-defined.

So in my first example:
 
myvector.push_back(std::move(bar)); 

vector.push_back() has an overload that takes an rvalue reference, which gets called because bar has been casted to an r-value. push_back() then probably attempts to use move-assignment or move-ctor defined by bar's class, which in this case is std::string. I'm guessing that std::string also defines a move-ctor/assignment which performs the pointer manipulation as you've described.


And in my second example:
 
_name(std::move(name))

in the initializer list would have invoked std::string's move-ctor. However, had the type of _name been of some type that didn't define a move-ctor, a copy would have been made (assuming that the copy-ctor was not deleted).

Does that sound right?

Last edited on
ElusiveTau wrote:
1
2
3
4
5
int&& Incr(int&& temp)
{
    // ...
    return std::move(temp); // "return temp" causes a compiler error
}

This code is subtly and dangerously wrong.

If the argument to Incr is a prvalue expression, then the parameter will be bound to a materialized temporary object. The temporary object's lifetime is the same as the full-expression - i.e., it dies at the semicolon.

Declarations like
const int& x = Incr(2);
and
int&& y = Incr(2);
Do not further extend the lifetime of the temporary already bound to the parameter; both x and y are left as dangling references.

The solution is to return exactly int.

There's also the less-important matter of temp = ++temp, which I assume is just a typo.
Last edited on
mbozzi wrote:
The temporary object's lifetime is the same as the full-expression - i.e., it dies at the semicolon ... Do not further extend the lifetime of the temporary already bound to the parameter; both x and y are left as dangling references.


I see. Further down the rabbit hole I go.

https://stackoverflow.com/a/1116763/5972766

There's also the less-important matter of temp = ++temp, which I assume is just a typo.


Brain fart. Should just be temp++;
Last edited on
Topic archived. No new replies allowed.