0

I am trying to understand C++'s move semantics, move constructor, move assignment operator, std::move(). Let's consider the following example:

#include <iostream>

void swapWithMove(int& a, int& b) {
    int temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

int main() {
    int x = 5;
    int y = 10;

    std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;

    swapWithMove(x, y);

    std::cout << "After swap: x = " << x << ", y = " << y << std::endl;

    return 0;
}

This example exchanges two integer values via std::move(). I know that the example is more for educational purposes and is not the typical use case of move semantics. But still, I would like to understand properly what happens in the memory to x, y, temp, a and b when the swapWithMove function is being executed.

Thank you very much in advance!

gvg
  • 23
  • 3
  • 1
    Does this answer your question? [What is std::move(), and when should it be used and does it actually move anything?](https://stackoverflow.com/questions/3413470/what-is-stdmove-and-when-should-it-be-used-and-does-it-actually-move-anythi) – Richard Critten Aug 12 '23 at 16:03
  • 1
    Or perhaps this? https://stackoverflow.com/questions/27888873/copy-vs-stdmove-for-ints – Bill Lynch Aug 12 '23 at 16:04
  • 3
    Literally nothing happens in the memory when move semantics are used. It is only a mean to make overload resolution select overload taking an rvalue reference, if any. – user7860670 Aug 12 '23 at 16:29
  • 1
    "_is not the typical use case of move semantics_": As a consequence of what the comment above said, your use in your example is 100% exactly identical to the same code without `std::move` calls. – user17732522 Aug 12 '23 at 16:34
  • 1
    For integer types, copying and moving are exactly the same. `std::move` is pointless and confusing here. If you know what happens in memory for a swap function that copies the integer arguments you know what happens in memory when the integer arguments are moved. – Pete Becker Aug 12 '23 at 16:43
  • Read this: https://stackoverflow.com/a/21358433/576911. The answer appears to address just the naming part. But if you read the end of this answer, it gives a very good answer to your question: What exactly does move do? – Howard Hinnant Aug 12 '23 at 16:48

1 Answers1

4

Under the circumstances (swapping ints), move semantics won't make any difference at all, with any implementation of which I'm aware (and an exception would be somewhat surprising).

Move semantics largely come into to play when we're dealing with something like a vector that (mostly) stores a pointer to the data it contains. For example, let's consider a somewhat simplified implementation of std::vector:

template <class T>
class vector {
    T *data;
    std::size_t size_allocated;
    std::size_t size_in_use;
// ...

The big thing to notice here is that the vector structure itself doesn't contain any of the real data. It just contains a pointer to the data, which is allocated wherever its Allocator object gets it (but typically using the standard allocator, which gets memory from the free store.

Anyway, let's consider assignment for our vector:

vector &operator=(vector const &other) {
    if (size_allocated < other.size_allocated) {
        // reallocate our storage so we have enough room
    }
    size_in_use = other.size_in_use;    
    for (std::size_t i=0; i<other.size_in_use; i++)
        data[i] = other.data[i];
    return *this;
}

That's simplified a lot, but you get the general idea--step through all the elements, and copy each one from the old vector to the new one.

But, if the right-hand one is an rvalue, that means we don't need to preserve its contents. So we can just "steal" what it contains:

vector &operator=(vector &&other) {
    delete [] data; // simplified--really uses allocator object
    data = other.data;
    size_in_use = other.size_in_use;
    size_allocated = other.size_allocted;
    other.data = nullptr;
    other.size_allocated = 0;
    other.size_in_use = 0;
    return *this;
}

So, instead of individually copying each element, we just grab the pointer from the source, and turn the source into an empty vector. Very fast, regardless of its size. There is a slightly tricky way we can simplify this a bit though:

vector &operator=(vector &&other) {
    swap(data, other.data);
    swap(size_in_use, other.size_in_use);
    swap(size_allocated, other.size_allocated);

    return *this;
}

Just swap our contents with the other's contents. It'll then be destroyed, and what used to be our contents will be disposed of then.

Anyway: moving an int is generally no different from just a normal assignment. Same with most other scalar types (char, short, long, double, float, pointers, etc.) The differences arise for structured types, especially those that mostly contain a pointer to the real data they store.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • "_(and an exception would be somewhat surprising)_": And would have to be non-conforming. There is only one possible behavior allowed and that coincides with the one without `std::move`. – user17732522 Aug 12 '23 at 18:51
  • @user17732522: Unfortunately, I've been around long enough that I find non-conformance only somewhat surprising. But in this case, yes, it would be pretty blatantly non-conforming, not something obscure you could probably never notice. – Jerry Coffin Aug 12 '23 at 19:00
  • Maybe worth mentioning that it doesn't necessarily make a difference for structures types; if no move constructor/assignment operator is available, but the copy-version is, the rvalue reference will implicitly be converted to a lvalue reference to const and the copy version is used. E.g. a class may be implemented as `struct Foo{ Foo(); Foo(Foo const&); Foo& operator=(Foo const&); ...stuff other than constructors/assignment operators... };` – fabian Aug 12 '23 at 19:33
  • @JerryCoffin Your example is very instructive and understandable, thank you very much for that! So basically, move is extremely useful when the internal representation of a class uses pointers. And then we can just steal the pointer of a temporary object instead of dealing with deep copy. Also, I did not know that in my example (the swapWithMove function), there is no difference if I apply std::move() or not in the implementation. I thought that the implementation with std::move() is somehow more efficient than if I implemented the swap function without it. – gvg Aug 13 '23 at 12:34
  • @JerryCoffin Thanks for pointing out that there is no difference in terms of efficiency. – gvg Aug 13 '23 at 12:36