Reputation: 1522
Lets say I have the following class:
#include <vector>
class Foo
{
public:
Foo(const std::vector<int> & a, const std::vector<int> & b)
: a{ a }, b{ b } {}
private:
std::vector<int> a, b;
};
But now I want to account for the situations in which the caller of the constructor might pass temporaries to it and I want to properly move those temporaries to a
and b
.
Now do I really have to add 3 more constructors, 1 of which has a
as a rvalue reference, 1 of which has b
as a rvalue reference and 1 that only has rvalue reference arguments?
Of course this question generalizes to any number of arguments which are worthwhile to move and the number of required constructors would be arguments^2 2^arguments.
This question also generalizes to all functions.
What is the idiomatic way of doing this? Or am I completely missing something important here?
Upvotes: 1
Views: 51
Reputation: 275740
Really, you should take by value if move construction is very cheap.
This results in exactly 1 extra move over the ideal case in every case.
But if you really must avoid that, you can do this:
template<class T>
struct sink_of {
void const* ptr = 0;
T(*fn)(void const*) = 0;
sink_of(T&& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return std::move(*(T*)(ptr));
})
{}
sink_of(T const& t):
ptr( std::addressof(t) ),
fn([](void const*ptr)->T{
return *(T*)(ptr);
})
{}
operator T() const&& {
return fn(ptr);
}
};
which uses RVO/elision to avoid that extra move at the cost of a bunch of pointer-based overhead and type erasure.
Here is some test code that demonstrates that
test( noisy nin ):n(std::move(nin)) {}
test( sink_of<noisy> nin ):n(std::move(nin)) {}
differ by exactly 1 move-construct of a noisy
.
The "perfect" version
test( noisy const& nin ):n(nin) {}
test( noisy && nin ):n(std::move(nin)) {}
or
template<class Noisy, std::enable_if_t<std::is_same<noisy, std::decay_t<Noisy>>{}, int> = 0 >
test( Noisy && nin ):n(std::forward<Noisy>(nin)) {}
has the same number of copy/moves as the sink_of
version.
(noisy
is a type that prints information about what moves/copies it engages in, so you can see what gets optimized away by elision)
This is only worth it when the extra move
is important to eliminate. For a vector
it is not.
Also, if you have a "true temporary" you are passing, the by-value one is as good as the sink_of or "perfect" ones.
Upvotes: 2
Reputation: 30873
The usual approach is to pass by value, then move-construct the members from the parameters:
Foo(std::vector<int> a, std::vector<int> b)
: a{ std::move(a) },
b{ std::move(b) }
{}
If a copy is needed, it will be created by the caller and then moved-from to construct the member. If the caller passes a temporary (or other rvalue), no copy is made, only a single move.
For arguments that don't have an efficient move constructor, then accepting a reference to const is slightly more efficient, and I'd retain that.
None of this applies if the function doesn't need a copy of the passed value - continue to use a const ref if you don't modify the value and don't need it to live beyond the end of the function execution. Personally, I use pass-by-value-and-move liberally in my constructors, but rarely in my other functions.
Upvotes: 5