为什么不使用自动返回类型在此函数中使用clang删除副本?

时间:2019-05-21 22:35:00

标签: c++ c++11 clang c++17

我发现clang 8.x不会忽略gcc和msvc没有问题的模板化类对象的副本的情况。在我的实际应用程序中,这种多余的副本非常昂贵,因此我试图深入了解它,并最终更好地了解了何时在C ++ 17中执行复制省略和不执行复制省略。

该问题显示在下面的代码片段中。以自动返回类型声明的,返回命名类对象的函数在其主体中具有一个额外的副本构造。如果将返回值重新编码为返回未命名的临时值,则会发生省略。如果对函数进行了编码以显式返回该类的实例(而不是auto),则省略。

如果struct A没有模板参数,那么还将生成完全省略的代码。

该问题表明是否所有内容都不例外或允许内联(NOINLINE是这样,您可以在Godbolt中查看问题,而无需执行代码)。

// compiled with -O2 -std=c++17
#if defined(_MSC_VER) && !defined(__clang__)
#define NOINLINE __declspec(noinline)
#else
#define NOINLINE __attribute__((noinline))
#endif

template<int P>
struct A {
  int data = 0;
  NOINLINE explicit A(int data_) noexcept : data(data_) { }
  NOINLINE ~A() noexcept { }
  NOINLINE A(const A& other) noexcept : data(other.data) { }
};


template <int P>
NOINLINE auto return_auto_A_nrvo(const A<P>& a) noexcept {
/* clang 6.0 thru 8.0 doesn't elide copy of 'result': 
   gcc and msvc elide the copy as expected.
        mov     r14, rsp
        mov     rdi, r14
        call    A<0>::A(A<0> const&)
        mov     rdi, rbx
        mov     rsi, r14
        call    A<0>::A(A<0> const&)
        mov     rdi, r14
        call    A<0>::~A() [base object destructor]

* return A<P>(a); is fully optimized
*/
  A<P> result(a);
  return result;
}

template <int P>
NOINLINE A<P> return_A_nrvo(const A<P>& a) noexcept {
// NRVO with explicit return type: fully optimized
  A<P> result(a);
  return result;
}

template <int P>
NOINLINE auto return_auto_A_rvo(const A<P>& a) noexcept {
// RVO: fully optimized
  return A<P>(a);
}

NOINLINE int main() {
  auto a1 = A<1>(42);
  auto a2 = return_auto_A_nrvo(a1);
  auto a3 = return_A_nrvo(a1);
  auto a4 = return_auto_A_rvo(a1);

  return a2.data + a3.data + a4.data;
}

函数return_auto_A_nrvo()中的注释显示了由clang生成的带有未删除副本的代码。其他变体均生成完全省略的代码。如果类A没有模板参数,则该副本也会被删除。

此Godbolt链接显示了GCC,clang和msvc生成的代码:https://www.godbolt.org/z/FDAvQO

也许这只是clang错过的错误/错过的优化机会,而Brands G和M却没有。如果是这种情况,我将尝试找到合适的发布位置,以供c族修复。但是我觉得这里可能会有更深层次的事情发生,例如返回auto和返回模板化类对象之间的根本区别。我相信C ++ 17保证将始终出现未命名的RVO,但不能保证像我这样的命名的RVO -我想了解为什么会这样(以及为什么在这里适用)。

1 个答案:

答案 0 :(得分:2)

您怀疑,编译器不需要在此处删除副本,因此更多是“错失良机”

[class.copy.elision]说,在这种情况下,编译器可以允许运行,但不是必需的。

  

[...]在以下情况下允许使用此复制/移动操作的省略号,称为 copy elision (可以结合使用以消除多个副本):
  —当 expression 是以下项的名称时,在具有类返回类型的函数中的 return 语句中   与函数具有相同类型(忽略cv限定)的非易失性自动对象   返回类型,通过直接构造自动对象可以省略复制/移动操作   进入函数调用的返回对象