Eliminate copies when building class members

For two arbitrary objects of type T and U , which consist of such a class

 template <class T, class U> struct Comp { T t_m; U u_m; }; 

, what would be the optimal way (from the point of view of minimizing copy operations) of creating them from the (available) time series?

I considered moving them to 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 motion constructors behave , or if they have something .

Since it seems that my class can be an aggregate, I was wondering if I could remove the constructor and allow aggregate initialization better, i.e. writing code as follows:

 Comp{ get_temporary_T(), get_temporary_U() }; 

or if there is an advantage to using direct initialization .

PS

The construction site (using the operator's location of the new operator) is not the solution I'm looking for.

PS 2

I suggest that std::tuple uses such an optimal method, since make_tuple shown to use temporary ones by invoking the tuple constructor:

 auto t = std::make_tuple(10, "Test", 3.14, std::ref(n), n); 

Can someone else clarify how this is done?

+1
source share
2 answers

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 will be a step. If it is not, it will be a copy - but then there is no way to make a copy somewhere. And it is not copied, then the whole question is controversial.

Of course, this only works for rvalues, so you need something for lvalues. This, unfortunately, is a bit more 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 resolve the derivation of Tx such that it is either T , T& , or D , D& , where D comes from T std::decay returns a reference and is_convertible for pointers, if one is received.

Ok, can we do better? Not really. This will either make 1 move or 1 instance for each participant. But what if we want to build them? We must be able to:

 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, we can potentially avoid any copy or even move by simply creating it in place. Whether it is profitable or not, depends on the use of Comp .

+2
source

Your suggestion with a move constructor seems to be the best approach.

As you deal with temporary objects, it is best to make (and most optimized) move the parameters in the members. But, as you said, perhaps the move constructors may not exist.

If there is only one parameter, then it is easy to check whether it is constructive, and move it and copy otherwise. You can use std::enable_if with std::is_move_constructible .

But with more than one parameter, you should check all combinations. For example, for two parameters you need to have 4 constructors: copy / copy, move / copy, copy / move and move / move. Thus, it is not scalable, and then it is more suitable for copying parameters.

With aggregate initialization, the parameters are not copied.

+1
source

Source: https://habr.com/ru/post/1245797/


All Articles