c++ - 为什么 clang 不使用自动返回类型省略此函数中的拷贝?

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

我发现了一个案例,其中 clang 8.x 没有省略 gcc 和 msvc 没有问题的模板化类对象的拷贝。在我的实际应用程序中,这个多余的拷贝非常昂贵,所以我试图深入了解这一点,并最终更好地理解在 C++17 中何时执行和不执行复制省略。

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

如果结构 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 则没有。如果是这样的话,我会尝试找到合适的地方发布这个,让 clang 的人修复。但我觉得这里可能有更深层次的原因,比如返回 auto 和返回模板类对象之间的根本区别。我相信 C++17 保证 unnamed-RVO 将始终发生,但不能保证在我的情况下命名的 RVO —— 我想了解为什么会这样(以及为什么它适用于此处)。

最佳答案

正如您所怀疑的,编译器不需要在此处省略拷贝,因此它更像是一个“错失的机会”

[class.copy.elision] 表示在这种情况下,编译器允许省略,但不是必需的。

[...] This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object [...] with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function call’s return object

关于c++ - 为什么 clang 不使用自动返回类型省略此函数中的拷贝?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56247149/

相关文章:

c++ - 使用imwrite OpenCV时断言失败

c++ - 将 C++ 11 的 for-range 语法用于顺序对的方法?

c++ - C++11 可变参数模板中的 va_arg() 是什么?

c++ - 如何使用 clang++ 进行编译?

c++ - LibCurl 如何同时捕获 400,而不是作为 CURLE_HTTP_RETURNED_ERROR 错误的一部分

c++ - opencv c++ 匹配来自文件的描述符

c++ - 为什么 Google 在成员变量之后命名访问器和修改器?

c++ - 右值参数的模板类型推导

c++ - 可变参数模板的无递归扩展到数组访问

c++ - []<typename>(){} 是有效的 lambda 定义吗?