Why do we copy then move?

C++C++11Move Semantics

C++ Problem Overview


I saw code somewhere in which someone decided to copy an object and subsequently move it to a data member of a class. This left me in confusion in that I thought the whole point of moving was to avoid copying. Here is the example:

struct S
{
    S(std::string str) : data(std::move(str))
    {}
};

Here are my questions:

  • Why aren't we taking an rvalue-reference to str?
  • Won't a copy be expensive, especially given something like std::string?
  • What would be the reason for the author to decide to make a copy then a move?
  • When should I do this myself?

C++ Solutions


Solution 1 - C++

Before I answer your questions, one thing you seem to be getting wrong: taking by value in C++11 does not always mean copying. If an rvalue is passed, that will be moved (provided a viable move constructor exists) rather than being copied. And std::string does have a move constructor.

Unlike in C++03, in C++11 it is often idiomatic to take parameters by value, for the reasons I am going to explain below. Also see this Q&A on StackOverflow for a more general set of guidelines on how to accept parameters.

> Why aren't we taking an rvalue-reference to str?

Because that would make it impossible to pass lvalues, such as in:

std::string s = "Hello";
S obj(s); // s is an lvalue, this won't compile!

If S only had a constructor that accepts rvalues, the above would not compile.

> Won't a copy be expensive, especially given something like std::string?

If you pass an rvalue, that will be moved into str, and that will eventually be moved into data. No copying will be performed. If you pass an lvalue, on the other hand, that lvalue will be copied into str, and then moved into data.

So to sum it up, two moves for rvalues, one copy and one move for lvalues.

> What would be the reason for the author to decide to make a copy then a move?

First of all, as I mentioned above, the first one is not always a copy; and this said, the answer is: "Because it is efficient (moves of std::string objects are cheap) and simple".

Under the assumption that moves are cheap (ignoring SSO here), they can be practically disregarded when considering the overall efficiency of this design. If we do so, we have one copy for lvalues (as we would have if we accepted an lvalue reference to const) and no copies for rvalues (while we would still have a copy if we accepted an lvalue reference to const).

This means that taking by value is as good as taking by lvalue reference to const when lvalues are provided, and better when rvalues are provided.

P.S.: To provide some context, I believe this is the Q&A the OP is referring to.

Solution 2 - C++

To understand why this is a good pattern, we should examine the alternatives, both in C++03 and in C++11.

We have the C++03 method of taking a std::string const&:

struct S
{
  std::string data; 
  S(std::string const& str) : data(str)
  {}
};

in this case, there will always be a single copy performed. If you construct from a raw C string, a std::string will be constructed, then copied again: two allocations.

There is the C++03 method of taking a reference to a std::string, then swapping it into a local std::string:

struct S
{
  std::string data; 
  S(std::string& str)
  {
    std::swap(data, str);
  }
};

that is the C++03 version of "move semantics", and swap can often be optimized to be very cheap to do (much like a move). It also should be analyzed in context:

S tmp("foo"); // illegal
std::string s("foo");
S tmp2(s); // legal

and forces you to form a non-temporary std::string, then discard it. (A temporary std::string cannot bind to a non-const reference). Only one allocation is done, however. The C++11 version would take a && and require you to call it with std::move, or with a temporary: this requires that the caller explicitly creates a copy outside of the call, and move that copy into the function or constructor.

struct S
{
  std::string data; 
  S(std::string&& str): data(std::move(str))
  {}
};

Use:

S tmp("foo"); // legal
std::string s("foo");
S tmp2(std::move(s)); // legal

Next, we can do the full C++11 version, that supports both copy and move:

struct S
{
  std::string data; 
  S(std::string const& str) : data(str) {} // lvalue const, copy
  S(std::string && str) : data(std::move(str)) {} // rvalue, move
};

We can then examine how this is used:

S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data

std::string bar("bar"); // bar is created
S tmp2( bar ); // bar is copied into tmp.data

std::string bar2("bar2"); // bar2 is created
S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data

It is pretty clear that this 2 overload technique is at least as efficient, if not more so, than the above two C++03 styles. I'll dub this 2-overload version the "most optimal" version.

Now, we'll examine the take-by-copy version:

struct S2 {
  std::string data;
  S2( std::string arg ):data(std::move(x)) {}
};

in each of those scenarios:

S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data

std::string bar("bar"); // bar is created
S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data

std::string bar2("bar2"); // bar2 is created
S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data

If you compare this side-by-side with the "most optimal" version, we do exactly one additional move! Not once do we do an extra copy.

So if we assume that move is cheap, this version gets us nearly the same performance as the most-optimal version, but 2 times less code.

And if you are taking say 2 to 10 arguments, the reduction in code is exponential -- 2x times less with 1 argument, 4x with 2, 8x with 3, 16x with 4, 1024x with 10 arguments.

Now, we can get around this via perfect forwarding and SFINAE, allowing you to write a single constructor or function template that takes 10 arguments, does SFINAE to ensure that the arguments are of appropriate types, and then moves-or-copies them into the local state as required. While this prevents the thousand fold increase in program size problem, there can still be a whole pile of functions generated from this template. (template function instantiations generate functions)

And lots of generated functions means larger executable code size, which can itself reduce performance.

For the cost of a few moves, we get shorter code and nearly the same performance, and often easier to understand code.

Now, this only works because we know, when the function (in this case, a constructor) is called, that we will be wanting a local copy of that argument. The idea is that if we know that we are going to be making a copy, we should let the caller know that we are making a copy by putting it in our argument list. They can then optimize around the fact that they are going to give us a copy (by moving into our argument, for example).

Another advantage of the 'take by value" technique is that often move constructors are noexcept. That means the functions that take by-value and move out of their argument can often be noexcept, moving any throws out of their body and into the calling scope (who can avoid it via direct construction sometimes, or construct the items and move into the argument, to control where throwing happens). Making methods nothrow is often worth it.

Solution 3 - C++

This is probably intentional and is similar to the copy and swap idiom. Basically since the string is copied before the constructor, the constructor itself is exception safe as it only swaps (moves) the temporary string str.

Solution 4 - C++

You don't want to repeat yourself by writing a constructor for the move and one for the copy:

S(std::string&& str) : data(std::move(str)) {}
S(const std::string& str) : data(str) {}

This is much boilerplate code, especially if you have multiple arguments. Your solution avoids that duplication on the cost of an unnecessary move. (The move operation should be quite cheap, however.)

The competing idiom is to use perfect forwarding:

template <typename T>
S(T&& str) : data(std::forward<T>(str)) {}

The template magic will choose to move or copy depending on the parameter that you pass in. It basically expands to the first version, where both constructor were written by hand. For background information, see Scott Meyer's post on universal references.

From a performance aspect, the perfect forwarding version is superior to your version as it avoids the unnecessary moves. However, one can argue that your version is easier to read and write. The possible performance impact should not matter in most situations, anyway, so it seems to be a matter of style in the end.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser2030677View Question on Stackoverflow
Solution 1 - C++Andy ProwlView Answer on Stackoverflow
Solution 2 - C++Yakk - Adam NevraumontView Answer on Stackoverflow
Solution 3 - C++JoeView Answer on Stackoverflow
Solution 4 - C++Philipp ClaßenView Answer on Stackoverflow