Eliminate copies when constructing members of a class

江枫思渺然 提交于 2020-01-03 03:43:13

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!