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?
"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?
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 &&
:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With