在构造类的成员时消除副本 [英] Eliminate copies when constructing members of a class
问题描述
对于类型 T
和 U
的两个任意对象组成类似
模板< class T,class U>
struct Comp
{
T t_m;
U u_m;
};
什么是最佳的(在最小化复制操作方面) (可用)临时?
我考虑过将它们移动到我的班级中
Comp(T& t,U& u)
:t_m(std :: move(t))
,u_m(std :: move u))
{}
但我不知道他们的移动构造函数的行为或如果他们有任何。
由于似乎我的类可以是一个聚合,我想知道是否删除构造函数并允许聚合初始化将是一个更好的解决方案,即编写如下代码:
Comp {get_temporary_T(),get_temporary_U()};
或如果使用直接初始化。
PS
就地构建(使用placement new操作符)不是我正在寻找的解决方案。
PS 2
我想像 std :: tuple
使用这种最佳方法,因为 make_tuple
通过调用元组构造函数显示为利用临时:
auto t = std :: make_tuple(10,Test,3.14,std :: ref(n),n);
可以有人详细说明这是如何完成的吗?
这已经是最佳的:
Comp(T& ,U&& u)
:t_m(std :: move(t))
,u_m(std :: move(u))
{}
如果 T
有一个移动构造函数,如果没有,这将是一个副本 - 但然后没有办法在某处复制。它是不可复制的,那么整个问题是模糊的。
当然,这只适用于右值,所以你也想要一些左值。不幸的是有点复杂:
模板< 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& (u))
{}
这里我们允许扣除 T
, T
, T&
C> D , D&
其中 D
来自 T
。 std :: decay
删除引用, is_convertible
用于指针检查是否派生。
好吧,我们能做得更好吗?不是真的。这是要为每个成员做1移动或1副本。但是如果我们想要构建它们呢?我们应该能够允许:
模板< class ... TArgs,class ... UArgs,
/ pre>
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 ,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 get< Is>(t)...)
,u_m(std :: get< Js(u)...)
{}
有了这个潜在的,我们可以通过就地构造来避免任何种类的复制或移动。这是否有益取决于您使用
Comp
。For two arbitrary objects of type
T
andU
that are composed into a class like sotemplate <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 sincemake_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 ?
解决方案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 eitherT
,T&
, orD
,D&
whereD
derives fromT
.std::decay
drops the reference andis_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
.这篇关于在构造类的成员时消除副本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!