优化编译器可以添加std :: move吗?

Edw*_*per 25 c++ optimization rvalue move-semantics c++11

如果编译器可以证明左值不会再次使用,它是否可以进行自动左值到右值转换?这是一个澄清我的意思的例子:

void Foo(vector<int> values) { ...}

void Bar() {
  vector<int> my_values {1, 2, 3};
  Foo(my_values);  // may the compiler pretend I used std::move here?
}
Run Code Online (Sandbox Code Playgroud)

如果将a std::move添加到注释行,则可以将矢量移动到Foo参数中,而不是复制.但是,正如所写,我没有使用std::move.

静态地证明my_values在注释行之后不会被使用,这很容易.那么编译器允许移动向量,还是需要复制它?

Yak*_*ont 33

如果复制发生vector在调用中,则编译器必须表现为Foo.

如果编译器可以证明存在一个有效的抽象机器行为,没有可观察到的副作用(在抽象机器行为中,而不是在真正的计算机中!),这涉及到移动std::vector进入Foo,它可以做到这一点.

在您的上述情况下,这(移动没有抽象机器可见的副作用)是真的; 但是,编译器可能无法证明它.

复制a时可能出现的可观察行为std::vector<T>是:

  • 在元素上调用复制构造函数.int无法观察到这样做
  • std::allocator<>在不同时间调用默认值.这调用::new::delete(可能是1)在任何情况下,::new::delete没有在上面的程序中被替换,所以你不能在标准下观察到这一点.
  • T在不同的对象上多次调用析构函数.没有观察到int.
  • vector调用后为非空Foo.没有人检查它,所以它是空的,如果它不是.
  • 外部向量元素的引用或指针或迭代器与内部元素不同.没有引用,向量或指针被赋予外部向量的元素Foo.

虽然您可能会说"但是如果系统内存不足,并且向量很大,那么这是不可观察的?":

抽象机器没有"内存不足"的情况,它只是std::bad_alloc因为非限制性原因而有时会失败(抛出).它没有失败是抽象机器的有效行为,并且没有失败的是不分配(实际)内存(在实际计算机上)也是有效的,只要内存的不存在没有可观察到的副作用.

一个稍微多一点的玩具箱:

int main() {
  int* x = new int[std::size_t(-1)];
  delete[] x;
}
Run Code Online (Sandbox Code Playgroud)

虽然这个程序明确地分配了太多的内存,但编译器可以自由地不分配任何东西.

我们可以走得更远.甚至:

int main() {
  int* x = new int[std::size_t(-1)];
  x[std::size_t(-2)] = 2;
  std::cout << x[std::size_t(-2)] << '\n';
  delete[] x;
}
Run Code Online (Sandbox Code Playgroud)

可以变成std::cout << 2 << '\n';.这个大缓冲区必须抽象地存在,但只要你的"真实"程序表现得像抽象机器一样,它实际上不必分配它.

不幸的是,以任何合理的规模这样做都很困难.信息可以通过C++程序泄漏的方式有很多种.所以依靠这样的优化(即使它们发生)也不会很好.


1 有一些关于合并呼叫的new内容可能会混淆问题,我不确定即使有替换也不合法跳过呼叫::new.


一个重要的事实是,有一些编译器的情况下表现为,如果有一个副本,即使需要std::move不叫.

当你return从一个看起来像return X;并且X是标识符的行中的函数的局部变量,并且该局部变量具有自动存储持续时间(在堆栈上)时,该操作隐式地是一个移动,并且编译器(如果它可以)可以将返回值和局部变量存在于一个对象中(甚至省略move).

从临时构造对象时也是如此 - 操作隐式地是移动(因为它绑定到右值)并且它可以完全消除移动.

在这两种情况下,编译器都需要将其视为移动(而不是副本),并且它可以忽略移动.

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return x;
}
Run Code Online (Sandbox Code Playgroud)

x没有std::move,但它被移动到返回值,并且该操作可被省略(x和返回值可以变成一个对象).

这个:

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return std::move(x);
}
Run Code Online (Sandbox Code Playgroud)

阻止elision,就像这样:

std::vector<int> foo(std::vector<int> x) {
  return x;
}
Run Code Online (Sandbox Code Playgroud)

我们甚至可以阻止此举:

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return (std::vector<int> const&)x;
}
Run Code Online (Sandbox Code Playgroud)

甚至:

std::vector<int> foo() {
  std::vector<int> x = {1,2,3,4};
  return 0,x;
}
Run Code Online (Sandbox Code Playgroud)

因为隐含行动的规则是故意脆弱的.(0,x是使用备受诟病的,运营商).

现在,,不建议依赖于在最后一个基础上不发生的隐式移动:标准委员会已经将隐式复制案例更改为隐式移动,因为隐式移动被添加到语言中因为它们认为它无害(其中函数返回A带有A(B&&)ctor 的类型,return语句是return b;where b的类型B;在执行副本的C++ 11版本中,现在它进行了移动.)不能排除进一步扩展隐式移动:显式地转换为a const&可能是现在和未来防止它的最可靠方法.

  • @ vu1p3n0x查找C++标准的副本?[这是一份草案](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf).[这是另一个](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf).C++的行为在标准中根据抽象机器指定. (2认同)