Copy-and-Swap idiom/Move Semantics

Hello, today I just learned about the copy-and-swap idiom. In my textbook, they also give an example of how to implement move assignment and move ctors using the swap method. Here is the definition of the respective functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
Spreadsheet::Spreadsheet(Spreadsheet&& src) noexcept
 : Spreadsheet()
{
  my_swap(*this, src);
}

Spreadsheet& Spreadsheet::operator=(Spreadsheet&& rhs) noexcept
{
  Spreadsheet temp(std::move(rhs));
  my_swap(*this, temp);
  return *this
}


Where my_swap(Spreadsheet&, Spreadsheet&) is a custom defined swap function using std::swap() to swap all data members, and the move ctor uses a private default ctor to initialize the object.

My question regards the following line in the move assignment operator:

Spreadsheet temp(std::move(rhs));

Why is it necessary to create a temporary object in such a way if rhs itself is an rvalue reference and is going to be destroyed at some point? Couldn't you just leave it like this:

1
2
3
4
5
Spreadsheet& Spreadsheet::operator=(Spreadsheet&& rhs) noexcept
{
  my_swap(*this, rhs);
  return *this
}
Last edited on
Why is it necessary to create a temporary object in such a way if rhs itself is an rvalue reference and is going to be destroyed at some point?
I suspect it's that whole "at some point" thing.
1
2
3
4
5
6
{
    Spreadsheet a, b;
    fillWith10GigOfData(a);
    a = std::move(b);
    doOtherStuffThatTakesForever();
}

At line 4, you expect all that data in a to be be released. But if you did it your way, it would just move to b. Then all that memory would still be in use while you do stuff at line 5.

Put another way, when you use a move assignment, you expect the data to be moved, not swapped.
Thank you. So what exactly happens to 'a' after the std::move cast is performed on it? It still exists, so is it still usable? And if so, what data would it contain? Would it all simply be set to zero?
It depends on how the move constructor is written. It is supposed to set the moved-from object to a valid state, but not necessarily "set to zero" (or an "empty" or "cleared" object), although that will often be the case. For example, std::vector would obviously set the moved-from object's data pointer to nullptr since nothing else makes sense, and therefore it would need to set its size and capacity to 0, setting the vector to a "cleared" state.
Last edited on
Thank you. One last thing. My textbook mentions because of the "rule of zero," that I could just implement my classes with standard library algorithms and not use raw pointers or manual dynamic allocation, in which case there is no need to define these manually. Is there any case, even though I have standard library algorithms like std::vector available, that I would have to define the move ctor/assignment operator or the other functions manually?
Last edited on
Yes. Here are two examples of when that'd happen:

* You have a non-copyable object (such as an std::unique_ptr) as a member of an object that you want to be copyable. It'd be up to you to write copy functions that do deep copies in that case.

* You want to change the state your move functions leave your moved-from object in (such as setting state flags, or allocating new objects for pointers that must always point to something).

-Albatross
Last edited on
A third example comes to mind, though a simple example case is a bit contrived.

There are times when, by design, a class must not share information as a copy would create. The contrived example, for simplicity, is if, for some reason, one uses a handle from a function like "open" from the C library. Other examples like that are device contexts (GUI programming), or their brushes and pens. These are all given to application code as a kind of integer (not a pointer). They actually represent allocated resources, but to the compiler they copy like an integer, so there's nothing which prohibits a copy in the way that a member std::unique_ptr or std::scoped_ptr would impose (the compiler would stop you, and you'd know you have to deal with moving, not copying).

However, by design, you really shouldn't have two of these held by different instances, because a file handle given by open must eventually be closed, and a brush or a pen deleted, or a device context released - and you really don't want to do those things twice (often leads to crashes or strange behaviors).

So, these would be moved, and copying would be denied.
Topic archived. No new replies allowed.