Short answer first: call by const& will always cost a copy. Depending on the conditions call by value might only cost one move. But it depends (please have a look at the code examples below for the scenarioa this table refers to):
lvalue rvalue unused lvalue unused rvalue
------------------------------------------------------
const& copy copy - -
rvalue&& - move - -
value copy, move move copy -
T&& copy move - -
overload copy move - -
So my executive summary would be that call by value is worth to be considered if
- move is cheap, since there might be an extra move
- the parameter is unconditionally used. Call by value also costs a copy if the parameter is not used e.g. because of an if clause or sth.
Call by value
Consider a function that is used to copy its argument
class Dog {
public:
void name_it(const std::string& newName) { names.push_back(newName); }
private:
std::vector<std::string> names;
};
In case of a lvalue passed to name_it
you´ll have two copy operations in case of an rvalue too. Thats bad because the rvalue could me moved.
One possible solution would be to write an overload for rvalues:
class Dog {
public:
void name_it(const std::string& newName) { names.push_back(newName); }
void name_it(std::string&& newName) { names.push_back(std::move(newName)); }
private:
std::vector<std::string> names;
};
That solves the problem and everything is fine, despite that you have two code two functions with exactly the same code.
Another viable solution would be to use perfect forwarding, but that also has several disadvantages, (e.g. perfect forwarding functions are quite greedy and render an existing overloaded const& function useless, typically they will need to be in a header file, they create several functions in the object code and some more.)
class Dog {
public:
template<typename T>
void name_it(T&& in_name) { names.push_back(std::forward<T>(in_name)); }
private:
std::vector<std::string> names;
};
Yet Another solution would be to use call by value:
class Dog {
public:
void name_it(std::string newName) { names.push_back(std::move(newName)); }
private:
std::vector<std::string> names;
};
The important thing is, as you mentioned the std::move
. This way you will have one function for both rvalue and lvalue. You will move rvalues but accept an additional move for lvalues, which might be fine if moving is cheap and you copy or move the parameter regardless of conditions.
So at the end I really think it´s plain wrong to recommend one way over the others. It strongly depends.
#include <vector>
#include <iostream>
#include <utility>
using std::cout;
class foo{
public:
//constructor
foo() {}
foo(const foo&) { cout << "\tcopy\n" ; }
foo(foo&&) { cout << "\tmove\n" ; }
};
class VDog {
public:
VDog(foo name) : _name(std::move(name)) {}
private:
foo _name;
};
class RRDog {
public:
RRDog(foo&& name) : _name(std::move(name)) {}
private:
foo _name;
};
class CRDog {
public:
CRDog(const foo& name) : _name(name) {}
private:
foo _name;
};
class PFDog {
public:
template <typename T>
PFDog(T&& name) : _name(std::forward<T>(name)) {}
private:
foo _name;
};
//
volatile int s=0;
class Dog {
public:
void name_it_cr(const foo& in_name) { names.push_back(in_name); }
void name_it_rr(foo&& in_name) { names.push_back(std::move(in_name));}
void name_it_v(foo in_name) { names.push_back(std::move(in_name)); }
template<typename T>
void name_it_ur(T&& in_name) { names.push_back(std::forward<T>(in_name)); }
private:
std::vector<foo> names;
};
int main()
{
std::cout << "--- const& ---\n";
{
Dog a,b;
foo my_foo;
std::cout << "lvalue:";
a.name_it_cr(my_foo);
std::cout << "rvalue:";
b.name_it_cr(foo());
}
std::cout << "--- rvalue&& ---\n";
{
Dog a,b;
foo my_foo;
std::cout << "lvalue: -\n";
std::cout << "rvalue:";
a.name_it_rr(foo());
}
std::cout << "--- value ---\n";
{
Dog a,b;
foo my_foo;
std::cout << "lvalue:";
a.name_it_v(my_foo);
std::cout << "rvalue:";
b.name_it_v(foo());
}
std::cout << "--- T&&--\n";
{
Dog a,b;
foo my_foo;
std::cout << "lvalue:";
a.name_it_ur(my_foo);
std::cout << "rvalue:";
b.name_it_ur(foo());
}
return 0;
}
Output:
--- const& ---
lvalue: copy
rvalue: copy
--- rvalue&& ---
lvalue: -
rvalue: move
--- value ---
lvalue: copy
move
rvalue: move
--- T&&--
lvalue: copy
rvalue: move