7

我试图解释为什么一个相当好的 C++ 11 编译器(clang)没有优化这段代码,并且想知道这里是否有人有意见。

#include <iostream>
#define SLOW

struct A {
  A() {}
  ~A() { std::cout << "A d'tor\n"; }
  A(const A&) { std::cout << "A copy\n"; }
  A(A&&) { std::cout << "A move\n"; }
  A &operator =(A) { std::cout << "A copy assignment\n"; return *this; }
};

struct B {
  // Using move on a sink. 
  // Nice talk at Going Native 2013 by Sean Parent.
  B(A foo) : a_(std::move(foo)) {}  
  A a_;
};

A MakeA() {
  return A();
}

B MakeB() {  
 // The key bits are in here
#ifdef SLOW
  A a(MakeA());
  return B(a);
#else
  return B(MakeA());
#endif
}

int main() {
  std::cout << "Hello World!\n";
  B obj = MakeB();
  std::cout << &obj << "\n";
  return 0;
}

如果我在#define SLOW注释掉并优化的情况下运行它,-s我会得到

Hello World!
A move
A d'tor
0x7fff5fbff9f0
A d'tor

这是预期的。

如果我在#define SLOW启用和优化的情况下运行它,-s我会得到:

Hello World!
A copy
A move
A d'tor
A d'tor
0x7fff5fbff9e8
A d'tor

这显然不是那么好。所以问题是:

为什么我没有看到在“慢”情况下应用了 NRVO 优化?我知道编译器不需要应用 NRVO,但这似乎是一个常见的简单案例。

一般来说,我尝试鼓励使用“SLOW”风格的代码,因为我发现它更容易调试。

4

2 回答 2

13

简单的答案是:因为在这种情况下不允许应用复制省略。编译器只允许在极少数和特定情况下应用复制省略。标准的引用是 12.8 [class.copy] 第 31 段:

...这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

  • 在具有类返回类型的函数的 return 语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作
  • [...]

显然B(a)is not的类型A,即不允许复制省略。同一段落中的其他项目符号指的是throw表达式、临时删除副本和异常声明等内容。这些都不适用。

于 2013-12-18T05:17:19.790 回答
3

您在慢速路径中看到的副本不是由于缺少 RVO 造成的,而是由于在 B(MakeA()) 中,“MakeA()”是一个右值,但在 B(a) 中,“a”是一个左值。

为了清楚起见,让我们修改慢速路径以指示 MakeA() 完成的位置:

#ifdef SLOW
  A a(MakeA());
  std::cout << "---- after call \n";
  return B(a);
#else

输出是:

Hello World!
---- after call 
A copy
A move
A d'tor
A d'tor
0x7fff5a831b28
A d'tor

这表明没有复制

A a(MakeA());

因此,RVO 确实发生了。

删除所有副本的修复是:

return B(std::move(a));
于 2014-01-05T23:45:23.847 回答