在构造类的成员时消除副本

时间:2016-03-26 01:02:07

标签: c++ c++11 c++14

对于两个TU类型的任意对象,它们组成一个类似的类

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

从(可用的)临时工作中构建它们的最佳方式(就最小化复制操作而言)是什么?

我考虑过&#34;移动&#34;他们进入我的班级

Comp(T&& t, U&& u)
    : t_m(std::move(t))
    , u_m(std::move(u))
{ }

但我不知道他们的移动构造者表现得如何或者他们是否有任何

由于我的类似乎可以是一个聚合,我想知道是否删除构造函数并允许aggregate initialization是一个更好的解决方案,即编写这样的代码:

Comp{ get_temporary_T(), get_temporary_U() }; 

或者使用direct initialization是否有优势。

PS

现场施工(使用安置新操作员)不是我正在寻找的解决方案。

PS 2

我想std::tuple使用这样一种最佳方法,因为make_tuple通过调用元组构造函数显示利用临时

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

或许有人会详细说明如何做到这一点?

2 个答案:

答案 0 :(得分:2)

这已经是最佳选择了:

Comp(T&& t, U&& u)
: t_m(std::move(t))
, u_m(std::move(u))
{ }

如果T有一个移动构造函数,那么这将是一个移动。如果它没有,那么这将是一个副本 - 但是那时无法在某个地方制作副本。而且它不可复制,那么整个问题都没有实际意义。

当然这只适用于右值,所以你也想要一些左手的东西。不幸的是,这有点复杂:

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))
{ }

在这里,我们希望允许扣除Tx,使其TT&DD&其中D 1}}派生自Tstd::decay删除引用,并is_convertible用于指针检查,如果它是派生的。

好的,我们可以做得更好吗?并不是的。这要么是为每个成员做1次移动,要么是1份。但是如果我们想要将它们构建到位呢?我们应该允许:

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)...)
{ }

有了这个,我们可以通过就地构建来避免任何类型的复制或移动。这是否有益取决于您对Comp的使用情况。

答案 1 :(得分:1)

你使用移动构造函数的命题似乎是最好的方法。

在处理临时对象时,最好的事情(以及最优化的)是在成员中移动参数。但正如你所说,移动构造函数可能不存在。

如果只有一个参数,则很容易检查它是否可移动构造并移动它并以其他方式复制它。您可以将std::enable_ifstd::is_move_constructible一起使用。

但是如果有超过1个参数,则必须检查所有组合。例如,对于2个参数,您必须具有4个构造函数:复制/复制,移动/复制,复制/移动和移动/移动。因此它不具有真正的可扩展性,因此更适合复制参数。

使用聚合初始化,参数是复制不移动。