【问题标题】:How to write move so that it can potentially be optimized away?如何编写 move 以便它可以被优化掉?
【发布时间】:2013-02-23 21:19:55
【问题描述】:

给定以下代码:

struct obj {
    int i;
    obj() : i(1) {}
    obj(obj &&other) : i(other.i) {}
};

void f() {
    obj o2(obj(obj(obj{})));
}

我希望发布版本只真正创建一个对象,而从不调用移动构造函数,因为结果与 就像我的代码已执行一样。虽然大多数代码并不是那么简单,但我可以想到一些难以预测的副作用,这些副作用可能会阻止优化器证明“好像”:

  1. 在移动构造函数或析构函数中对全局或“外部”事物的更改。
  2. 移动构造函数或析构函数中的潜在异常(无论如何可能是糟糕的设计)
  3. 内部计数或缓存机制发生变化。

由于我不经常使用其中任何一个,我可以期望我的大部分进出函数,这些函数后来被内联优化掉还是我忘记了什么?

附:我知道仅仅因为可以进行优化并不意味着它将由任何给定的编译器进行。

【问题讨论】:

  • 而不是希望 as-if 消除,您可以摆脱 std::move 调用,并且复制省略可能会删除该代码中的每一个复制/移动(即使这些移动操作已经副作用)。 (as-if 消除可能只是将o 从程序中完全删除)。

标签: c++ optimization c++11 move


【解决方案1】:

这实际上与 as-if 规则没有任何关系。允许编译器省略移动和复制,即使它们有一些副作用。这是允许编译器进行的单一优化,它可能会改变程序的结果。来自§12.8/31:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数有副作用。

因此编译器不必费心检查移动构造函数内部发生的情况,无论如何它很可能会摆脱这里的任何移动。为了证明这一点,请考虑以下示例:

#include <iostream>

struct bad_mover
{
  static int move_count;
  bad_mover() = default;
  bad_mover(bad_mover&& other) { move_count++; }
};

int bad_mover::move_count = 0;

int main(int argc, const char* argv[])
{
  bad_mover b{bad_mover(bad_mover(bad_mover()))};
  std::cout << "Move count: " << bad_mover::move_count << std::endl;
  return 0;
}
  1. g++ -std=c++0x编译:

    Move count: 0
    
  2. g++ -std=c++0x -fno-elide-constructors编译:

    Move count: 3
    

但是,我会质疑您提供具有额外副作用的移动构造函数的任何理由。无论副作用如何都允许这种优化的想法是,复制或移动构造函数不应该做除了复制或移动之外的任何事情。有复制或移动的程序应该和没有的完全一样。

不过,您无需致电std::movestd::move 用于将左值表达式更改为右值表达式,但创建临时对象的表达式已经是右值表达式。

【讨论】:

  • 哇,所以如果我编写移动对象 10 次并在移动构造函数中计数的代码,我可以获得小于 10 的值吗?很高兴知道!
  • +1。作为记录,这是从 §12.8/31 中引用的,并且“某些标准”确实包括您的移动构造函数的初始化情况。
  • @PorkyBrain 我添加了一个示例来证明这一点。
【解决方案2】:

使用std::move( tmp(...) ) 完全没有意义,临时的tmp 已经是一个右值,你不需要使用std::move 将它转换为一个右值。

阅读本系列文章:Want Speed? Pass By Value

通过在 Stackoverflow 上提问,你会比你学到更多,理解得更好

【讨论】:

  • 是的,你说得对,我删除了 std::move,我的例子不好,但我关于副作用的问题仍然存在。
  • 编译器可以完全忽略省略副本或移动时的副作用。如果初始化有资格进行省略,那么编译器可以这样做,而无需关心副作用、可能的异常或其他任何事情。我已经修复了你的代码,所以它可以编译,当我运行它时o2.i1。阅读我链接到的文章和 sftrabbit 的回答
  • std::move(other.i) 也完全没有意义,int 没有资源可以移动,所以移动只是一个副本
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-21
  • 2013-11-09
  • 1970-01-01
  • 2012-03-13
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
相关资源
最近更新 更多