Search

15.4 — std::move

Once you start using move semantics more regularly, you’ll start to find cases where you want to invoke move semantics, but the objects you have to work with are l-values, not r-values. Consider the following swap function as an example:

Passed in two objects of type T (in this case, std::string), this function swaps their values by making three copies. Consequently, this program prints:

x: abc
y: de
x: de
y: abc

As we showed last lesson, making copies can be inefficient. And this version of swap makes 3 copies. That leads to a lot of excessive string creation and destruction, which is slow.

However, doing copies isn’t necessary here. All we’re really trying to do is swap the values of a and b, which can be accomplished just as well using 3 moves instead! So if we switch from copy semantics to move semantics, we can make our code more performant.

But how? The problem here is that parameters a and b are l-value references, not r-value references, so we don’t have a way to invoke the move constructor and move assignment operator instead of copy constructor and copy assignment. By default, we get the copy constructor and copy assignment behaviors. What are we to do?

std::move

In C++11, std::move is a standard library function that serves a single purpose -- to convert its argument into an r-value. We can pass an l-value to std::move, and it will return an r-value reference. std::move is defined in the utility header.

Here’s the same program as above, but with a myswap() function that uses std::move to convert our l-values into r-values so we can invoke move semantics:

This prints the same result as above:

x: abc
y: de
x: de
y: abc

But it’s much more efficient about it. When tmp is initialized, instead of making a copy of x, we use std::move to convert l-value variable x into an r-value. Since the parameter is an r-value, move semantics are invoked, and x is moved into tmp.

With a couple of more swaps, the value of variable x has been moved to y, and the value of y has been moved to x.

Another example

We can also use std::move when filling elements of a container, such as std::vector, with l-values.

In the following program, we first add an element to a vector using copy semantics. Then we add an element to the vector using move semantics.

This program prints:

Copying str
str: Knock
vector: Knock

Moving str
str:
vector: Knock Knock

In the first case, we passed push_back() an l-value, so it used copy semantics to add an element to the vector. For this reason, the value in str is left alone.

In the second case, we passed push_back() an r-value (actually an l-value converted via std::move), so it used move semantics to add an element to the vector. This is more efficient, as the vector element can steal the string’s value rather than having to copy it. In this case, str is left empty.

At this point, it’s worth reiterating that std::move() gives a hint to the compiler that the programmer doesn’t need this object any more (at least, not in its current state). Consequently, you should not use std::move() on any persistent object you don’t want to modify, and you should not expect the state of any objects that have had std::move() applied to be the same after they are moved!

Move functions should always leave your objects in a well-defined state

As we noted in the previous lesson, it’s a good idea to always leave the objects being stolen from in some well-defined (deterministic) state. Ideally, this should be a “null state”, where the object is set back to its uninitiatized or zero state. Now we can talk about why: with std::move, the object being stolen from may not be a temporary after all. The user may want to reuse this (now empty) object again, or test it in some way, and can plan accordingly.

In the above example, string str is set to the empty string after being moved (which is what std::string always does after a successful move). This allows us to reuse variable str if we wish (or we can ignore it, if we no longer have a use for it).

Where else is std::move useful?

std::move can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. In previous lessons, we’ve had to resort to copy-semantics to do the swapping. Now we can use move semantics, which is more efficient.

It can also be useful if we want to move the contents managed by one smart pointer to another.

Conclusion

std::move can be used whenever we want to treat an l-value like an r-value for the purpose of invoking move semantics instead of copy semantics.


15.5 -- std::unique_ptr
Index
15.3 -- Move constructors and move assignment

95 comments to 15.4 — std::move

  • spaceship_driver

    hey i am able to use std::move without using "utility" header in VS code.

    • nascardriver

      That's why we included a comment in the examples

      Note that "in VS code" provides no information about the compiler you're using. VS code is a text editor and has no effect on the errors you're getting. I think you can see which compiler you're using in the .vscode/c_cpp_properties.json file.

  • Constantinos

    Hey,

    I tried to implement the swapping of variables with a bit different way, which seems to work the same way with the example presented here. I just wonder whether there are any essential (underlying) differences between the two cases or if my code has any troubles in terms of cpp best practices.
    The code is the following:

    Thanks for this detailed and noob-friendly tutorial series! :)

    Cheers,

    Constantinos

    • nascardriver

      Careful with list initialization and templates.

      This will not copy `a` if `T` has a list-constructor that take a list of `T`. With templates, it's better to use non-list direct initialization.

      You're missing a call to `std::move` in line 52, it will use the copy constructor.

      Use single quotes for characters. Strings are more expensive.

      • Constantinos

        Thank you for your answer.

        I am sorry, but I am missing something: why do I need to use std::move() in line 51? I thought that using std::move() when passing 'a' into the function in line 26 would do the work.

        Since I am wrong about this, mustn't I use std::move() in the assignments in lines 53 and 54 as well?

        • nascardriver

          `a` and `b` are references to r-values, but they themselves are l-values. Whenever you want to preserve the r-valueness of something, you need to use `std::move` (Or `std::forward`).

          I missed line 53 and 54, they also need `std::move`. Good point :)

  • Vissa

    Hello, just a short question. Is

    for an object O of type T essentially equivalent to

    ?

    Thanks in advance

    • nascardriver

      Yep, it's the same.

      • Vissa

        Then why is it that "you should not expect the state of any objects that have had std::move() applied to be the same after they are moved"? Doesn't this just create a temporary variable leaving the initial unchanged?

        • nascardriver

          A cast to T&& doesn't create a copy. A function that takes a parameter by T&& is allowed to, and expected to, modify the object. It's almost the same as passing by reference and telling the function "Do with this object whatever you want, I won't use it anymore".

  • Hassan

    Hello,
    std::move function don't work in this exemple:

    #include <iostream>
    #include <string>
    #include <utility>
    #include <vector>

    using namespace std;

    template<class T>
    void myswap(T& a, T& b)
    {
      T tmp { a };
            std::cout << "a: " << a << '\n';
        std::cout << "b: " << b << '\n';
        std::cout << "tmp: " << tmp << "\n\n";
      a = move(b);
            std::cout << "a: " << a << '\n';
        std::cout << "b: " << b << '\n';
        std::cout << "tmp: " << tmp << "\n\n";
      b = move(tmp);
            std::cout << "a: " << a << '\n';
        std::cout << "b: " << b << '\n';
            std::cout << "tmp: " << tmp << "\n\n";
    }

    int main()
    {
        std::string x{ "1" };
        std::string y{ "2" };

        std::cout << "x: " << x << '\n';
        std::cout << "y: " << y << "\n\n";

        myswap(x, y);

        std::cout << "x: " << x << '\n';
        std::cout << "y: " << y << "\n\n";

        return 0;
    }
    program prints:

    x: 1
    y: 2

    a: 1
    b: 2
    tmp: 1

    a: 2
    b: 1
    tmp: 1

    a: 2
    b: 1
    tmp: 1

    x: 2
    y: 1

    but if i use std::move in "T tmp { move(a) }" the function std::move work correctly and program prints:

    x: 1
    y: 2

    a:
    b: 2
    tmp: 1

    a: 2
    b:
    tmp: 1

    a: 2
    b: 1
    tmp:

    x: 2
    y: 1

    I don't understand why you don't work in the first case.

    • nascardriver

      What do you mean by "don't work". Both functions swap the values, that's what's supposed to happen.

      • hassan

        why values of b and tmp not empty in the first exemple Although i use the the function std::move.

            a = move(b);
            std::cout << "a: " << a << '\n';
            std::cout << "b: " << b << '\n';
            std::cout << "tmp: " << tmp << "\n\n";

            a: 2
            b: 1  //b is not empty
            tmp: 1

             b = move(tmp);
             std::cout << "a: " << a << '\n';
             std::cout << "b: " << b << '\n';
             std::cout << "tmp: " << tmp << "\n\n";

             a: 2
             b: 1
             tmp: 1 // tmp is not empty

        • nascardriver

          Moving from a `std::string` leaves the string in an unspecified state, it may or may not still hold a value. Even though the state is unspecified, it's a safe state and won't lead to double frees.

          For me (clang++9), your code produces

          As you can see, after every move, the string is empty (It doesn't have to be empty!).

Leave a Comment

Put all code inside code tags: [code]your code here[/code]