Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11: Do move semantics get involved for pass by value?

I've got an API that looks like this:

void WriteDefaultFileOutput(std::wostream &str, std::wstring target)
{
    //Some code that modifies target before printing it and such...
}

I'm wondering if it'd be sensible to enable move semantics by doing this:

void WriteDefaultFileOutput(std::wostream &str, std::wstring&& target)
{
    //As above
}
void WriteDefaultFileOutput(std::wostream &str, std::wstring const& target)
{
    std::wstring tmp(target);
    WriteDefaultFileOutput(str, std::move(tmp));
}

or is this just boilerplate that the compiler should be able to figure out anyway?

like image 909
Billy ONeal Avatar asked Mar 15 '12 17:03

Billy ONeal


2 Answers

"Pass by value" can mean either copy or move; if the argument is an lvalue, the copy constructor is invoked. If the argument is an rvalue, the move constructor is invoked. You don't have to do anything special involving rvalue references to get move semantics with pass by value.

I dig deeper into this topic in What are move semantics?

like image 78
fredoverflow Avatar answered Sep 30 '22 18:09

fredoverflow


You should prefer pass-by-value if you're going to make a copy with non-movable values. For example, your const& version of WriteDefaultFileOutput explicitly copies the parameter, then moves the copy with the move version. Which means if WriteDefaultFileOutput is called with a movable value (xvalue or prvalue), then it will be moved, and if it's called with an lvalue, it will be copied.

This means that there is no difference between the two forms of this function. Consider this:

WriteDefaultFileOutput(L"SomeString");

In your first case, it will create a temporary wstring. Temporaries are prvalues, so it will be "moved" into the parameter (since wstring has a move constructor). Of course, any compiler worth its salt will elide the move and simply construct the temporary directly into the parameter.

In your second case, more or less the same thing happens. A temporary wstring is created. Temporaries are prvalues, so they can bind to && parameter types. Therefore, it will call the first version of your function with an r-value reference to the temporary. The only possible difference would be from a compiler that won't elide the move. And even then, a move isn't that expensive (depending on your basic_string implementation).

Now consider this:

std::wstring myStr{L"SomeString"};
WriteDefaultFileOutput(myStr);

In the first case, the call to WriteDefaultFileOutput will cause a copy of the myStr value into the function parameter.

In the second case, myStr is an lvalue. It cannot bind to a && parameter. Therefore, the only version it can call is the const& version. That function will manually construct a copy, and then move the copy with the other one.

An identical effect. Your first version has less code, so go with that for obvious reasons.

In general, I would say that there are only two reasons to take a parameter as a &&:

  1. You are writing a move constructor.
  2. You are writing a forwarding function and need to use perfect forwarding.

In all other cases where you want movement to be possible, just take a value. If the user wants to copy, let them copy. I suppose if you want to explicitly forbid copying of the parameter, you could take a &&. But the main issue is one of clarity.

If you take a value parameter, and the user provides a moveable value, then the user-provided value will always be moved. For example, your && version of WriteDefaultFileOutput does not have to actually move the data from its parameter. It certainly can. But it doesn't have to. If it took a value, then it would already have claimed the data.

Therefore, if a function takes a value parameter, and you see a std::move into that value, then you know that the object which was moved is now empty. It is guaranteed to have been moved from.

like image 29
Nicol Bolas Avatar answered Sep 30 '22 16:09

Nicol Bolas