为什么不在这个具有自动返回类型的函数中省略复制?

Why isn't clang eliding copy in this function with auto return type?

本文关键字:函数 中省 复制 返回类型 为什么不      更新时间:2023-10-16

我发现了一个案例,clang 8.x并没有将gcc和msvc没有麻烦的模板类对象的副本。在我的实际应用程序中,此多余的副本非常昂贵,因此我试图陷入困境,最终可以更好地了解Copy Elision何时并且在C 17中没有执行。

问题显示在下面的代码段中。用自动返回类型声明的函数返回命名类对象的函数在其体内具有额外的副本构造。如果重新编码返回以返回未命名的临时性,则会发生这种情况。如果重新编码该函数以明确返回类的实例(而不是自动(,则会发生。

如果struct a没有模板参数,则还会生成完全删除的代码。

问题表明,所有内容是否都不是noexcept或被允许在线(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错过了G和M品牌G和M。如果是这样,我将尝试找到适当的位置将其发布给Clang Colks修复。但是我觉得这里可能会发生一些更深入的事情,例如返回自动和返回模板类对象之间的根本区别。我相信C 17保证将始终发生未命名的RVO,但是在我的情况下,该名称为RVO - 我不保证 - 我想了解为什么是这种情况(以及为什么在此处适用(。

当您怀疑时,编译器不需要在此处填写副本,因此它更像是一个"错过的机会"

[class.copy.elision]说,在这种情况下,编译器为允许 eelide,但不需要。

[...]在以下情况下,允许使用复制/移动操作(称为 copy Elision ((可以合并以消除多个副本(:
- 在中 具有相同类型(忽略cv-qualification(的非挥发性自动对象[...] 返回类型,可以通过直接构造自动对象来省略复制/移动操作 进入函数调用的返回对象