【问题标题】:Eliminate copies when constructing members of a class构造类成员时消除副本
【发布时间】:2016-03-26 01:02:07
【问题描述】:

对于TU 类型的两个任意对象,它们组合成这样的类

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

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

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

Comp{ get_temporary_T(), get_temporary_U() }; 

或者如果使用direct initialization 有优势。

PS

就地构造(使用placement new 运算符)不是我正在寻找的解决方案。

PS 2

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

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

或者有人可以详细说明这是如何完成的吗?

【问题讨论】:

  • 太宽泛了? “从(可用的)临时文件中构建它们的最佳(就最小化复制操作而言)方法是什么?”太宽泛了?
  • 在开始尝试超越编译器的优化器之前,为什么不先测试最简单的代码并开启优化?
  • @PaulMcKenzie 你能帮我解决这个问题吗?您的意思是检查机器代码、速度基准测试、检测我的代码吗?
  • "我想 std::tuple 使用了这样一种最佳方法,因为 make_tuple 显示为通过调用元组构造函数来利用临时对象:" 不清楚你指的是什么在这里,或者make_tuple 的操作方式以任何方式比您的第一个选项更优化。
  • 附带说明,您实际上可以检查您的类型是否有移动构造函数:en.cppreference.com/w/cpp/types/is_move_constructible

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


【解决方案1】:

这已经是最优的了:

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&amp;,要么是 DD&amp;,其中 D 派生自 Tstd::decay 删除引用,is_convertible for pointers 检查它是否是派生的。

好的,我们能做得更好吗?并不真地。这将为每个成员执行 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 的使用。

【讨论】:

  • 太棒了!另外,std::tuple 现在是否支持分段构造? (我之前没有听说过这个,在搜索时我发现了this
【解决方案2】:

您对移动构造函数的提议似乎是最好的方法。

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

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

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

通过聚合初始化,参数是复制而不是移动。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多