为什么clang没有用NRVO优化这个?

时间:2013-12-18 04:58:12

标签: c++ optimization c++11 nrvo

我试图解释为什么一个相当不错的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

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

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

总的来说,我尝试鼓励“SLOW”风格的代码,因为我觉得它更容易调试。

2 个答案:

答案 0 :(得分:13)

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

  

......在下列情况下(允许合并以消除多份副本),允许复制/移动操作(称为复制省略)的省略:

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

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

答案 1 :(得分:2)

您在慢速路径中看到的副本不是由于缺少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));