问题
For two arbitrary objects of type T
and U
that are composed into a class like so
template <class T, class U>
struct Comp
{
T t_m;
U u_m;
};
what would be the optimum (in terms of minimizing copy operations) way to construct them out of (available) temporaries ?
I have considered "moving" them into my class
Comp(T&& t, U&& u)
: t_m(std::move(t))
, u_m(std::move(u))
{ }
but I don't know how well their move constructors behave or if they have any whatsoever.
Since it seems that my class can be an aggregate I was wondering whether removing the constructor and allowing aggregate initialization would be a better solution, i.e. writing code like this:
Comp{ get_temporary_T(), get_temporary_U() };
or if there's an advantage in using direct initialization.
PS
In place construction (using placement new operator) is not the solution I'm looking for.
PS 2
I imagine std::tuple
uses such an optimum method since make_tuple is shown to utilize temporaries by calling the tuple constructor :
auto t = std::make_tuple(10, "Test", 3.14, std::ref(n), n);
could alternatively someone elaborate on how this is done ?
回答1:
This is already optimal:
Comp(T&& t, U&& u)
: t_m(std::move(t))
, u_m(std::move(u))
{ }
If T
has a move constructor, this'll be a move. If it doesn't, this'll be a copy - but then there's no way around making a copy somewhere. And it isn't copyable, then the whole question is moot.
Of course this only works for rvalues, so you'd want something for lvalues too. That unfortunately gets a little complicated:
template <class Tx, class Ux,
class = std::enable_if_t<std::is_convertible<std::decay_t<Tx>*, T*>::value &&
std::is_convertible<std::decay_t<Ux>*, U*>::value>>
Comp(Tx&& t, Ux&& u)
: t_m(std::forward<Tx>(t))
, u_m(std::forward<Ux>(u))
{ }
Here we want to allow deduction of Tx
such that it's either T
, T&
, or D
, D&
where D
derives from T
. std::decay
drops the reference and is_convertible
for pointers checks if it's derived.
Okay, can we do better? Not really. This is either going to do 1 move or 1 copy for each member. But what if we want to construct them in place? That we should be able to allow for:
template <class... TArgs, class... UArgs,
class = std::enable_if_t<std::is_constructible<T, TArgs...>::value &&
std::is_constructible<U, UArgs...>::value>>
Comp(std::piecewise_construct_t pc, std::tuple<TArgs...> const& t, std::tuple<UArgs...> const& u)
: Comp(t, u, std::index_sequence_for<TArgs...>{}, std::index_sequence_for<UArgs...>{})
{ }
private:
template <class TTuple, class UTuple, size_t... Is, size_t... Js>
Comp(TTuple const& t, UTuple const& u, std::index_sequence<Is...>, std::index_sequence<Js...> )
: t_m(std::get<Is>(t)...)
, u_m(std::get<Js>(u)...)
{ }
With this potentially we can avoid any kind of copy or move at all by just constructing in-place. Whether or not this is beneficial depends on your usage of Comp
.
回答2:
Your proposition with the move constructor seems to be the best approach.
As you deal with temporary objects, the best thing to do (and the most optimized) is to move the parameters in the members. But as you said maybe the move constructors may not exist.
If there is only one parameter it is easy to check if it is move constructible and move it and copy it otherwise. You can use std::enable_if
with std::is_move_constructible
.
But with more than 1 parameter, you have to check all the combinations. For instance for 2 parameters you have to have 4 constructors that do: copy/copy, move/copy, copy/move and move/move. So it is not really scalable and then it is more suitable to copy the parameters.
With the aggregate initialization the parameters are copy not move.
来源:https://stackoverflow.com/questions/36230428/eliminate-copies-when-constructing-members-of-a-class