【问题标题】:Assignment without copy in std move在 std move 中没有副本的分配
【发布时间】:2019-01-08 23:41:42
【问题描述】:

我有一个返回地图的 util 函数

std::map getFooMap() {
  std::map foo;
  // ... populate the map
  return foo;
}

从调用方,我想将地图分配给某个对象的数据字段。我能做到:

dest.data = getFooMap()

这会比下面的更快吗?

auto temp = getFooMap();
dest.data = std::move(temp);

我认为这应该是因为我避免了额外的副本?

【问题讨论】:

  • 好的编译器会优化这两个调用以实现几乎相同的效果,但如果不是这种情况,则首选第一个。为什么要立即将返回的变量命名为std::move?
  • 您检查过组件吗? FWIW,我听说std::move 抑制NRVO,所以在一个好的编译器上,我希望无移动选项更快或至少与std::move 一样快。
  • 最好能构造 dest.data 调用getFooMap(),这通常意味着在构造dest 时进行调用。这对你来说可能吗?

标签: c++ c++11 move rvo nrvo


【解决方案1】:

我认为这应该是因为我避免了额外的副本?

只要“std::map”是可移动的,您就只能避免一次额外的移动——优化器也可以避免这种移动。

性能差异可能可以忽略不计或根本不存在,但dest.data = getFooMap() 更简单并且可能不会更慢。

正如评论中所指出的,直接初始化 dest.data 而不是在构造后分配它会更快。这可以通过在成员初始化程序中调用 getFooMap 来实现。

【讨论】:

    【解决方案2】:

    从美学的角度来看,它必须是第一个变体。您必须对语言标准和编译器作者有一定的信心,才能不需要像临时这样的愚蠢工件来促进此处的优化。

    让我们检查一下是否确实如此。首先,请允许我稍微抽象一下您的示例并将map 替换为foo 类,而不定义其各种方法;并假设什么都不会引发任何异常:

    #include <utility>
    
    // Originally this was an std::map, replaced with
    // an "opaque" class to shorten the output and
    //  prevent inlining and conflation of 
    // std-map-related code with the rest of the code
    struct foo {
        foo() noexcept;
        foo(const foo&) noexcept;
        foo(foo&&) noexcept;
        foo& operator=(foo&&) noexcept;
        foo& operator=(const foo&) noexcept;
        ~foo() noexcept;
    };
    
    struct dest_t { foo data; };
    
    foo get_foo() noexcept;
    void do_stuff_with(const dest_t& dest) noexcept;
    
    void move_from_intermediate() noexcept {
        dest_t dest;
        auto temp = get_foo();
        dest.data = std::move(temp);
        do_stuff_with(dest);
    }
    
    void straight_assignment() noexcept {
        dest_t dest;
        dest.data = get_foo();
        do_stuff_with(dest);
    }
    

    现在,如果我们compile this on GodBolt,我们会看到 GCC(主干)为这两个函数生成相同的汇编代码。其中包括:

    • 1 建设
    • 2 次破坏
    • 1 次移动分配
    • 1 次致电get_foo()
    • 1 次致电do_stuff_with()

    在这两种情况下。 clang (trunk) 也将产生相同的代码,只是稍微重新排序 - 但与 GCC 的代码不完全相同。

    【讨论】:

      猜你喜欢
      • 2016-09-10
      • 2015-07-15
      • 2011-04-12
      • 1970-01-01
      • 2021-08-17
      • 2013-08-06
      • 1970-01-01
      • 2017-08-19
      • 2018-02-07
      相关资源
      最近更新 更多