On 30/03/2017 20:34, Rainer Deyke wrote:
On 30.03.2017 00:12, Gavin Lambert wrote:
const& parameters should be your default for anything with non-trivial copy/move behaviour anyway, so you'd only run into this issue in already-bad code.
Passing by const& can actually force extra copies to be created that would not be created with pass-by-value.
Consider:
std::string f();
struct X { void by_value(std::string s) { this->some_member = std::move(s); }
void by_const_ref(std::string const& s) { // Cannot move from const& this->some_member = s; }
std::string some_member; };
X x;
// The following line creates no copies. x.by_value(f());
// The following line must create an extra copy. x.by_const_ref(f());
True, but in my experience it's a rare case. It replaces a single copy with two moves, and since moves can decay to copies (in types that implement a copy constructor without a move constructor, which is quite a lot of older code), it's quite likely to be a pessimisation in the general case. Passing by value as a means to take advantage of rvalue move semantics and copy elision is *only* a good choice if you can prove that all of the following are true: 1. The type has a move constructor which is at least twice as fast as the copy constructor. (This is an easy bar to meet if the copy constructor needs to allocate heap memory, and borderline for a shared_ptr copy.) 2. The body of the method *always* (and I do mean really always) needs a copy of the parameter (eg. to initialise a member field or create a lambda/binding -- this is an easy bar to meet for constructors and setters, but very uncommon for any other method). If there is an execution path that doesn't need to make that copy, then it's a pessimisation whenever that path is taken (an avoidable copy -- which you can usually ignore for exception-throw paths, but not other conditions). 3. The method is very likely to be called with both lvalue and rvalue parameters, either equally or with a higher incidence of rvalues. (If most calls are with lvalues with few or no rvalues, it's typically not worth it, because that would be replacing a single copy with a copy+move.) Note that you *can't* meet the first condition for generic templated-type code -- type traits tell you nothing about performance and you can't even detect whether a type actually *has* a "real" move constructor (rather than decaying to a copy constructor). Using multiple overloads or perfect forwarding is generally the best choice for generic code instead. And it's hard (though not impossible) to justify the last condition in library code.