Почему в этой функции нет скрытой копии с автоматическим типом возврата? - PullRequest
5 голосов
/ 22 мая 2019

Я обнаружил случай, когда clang 8.x не удаляет копию объекта шаблонного класса, с которым gcc и msvc не имеют проблем. В моем реальном приложении эта лишняя копия довольно дорогая, поэтому я пытаюсь разобраться в этом и в итоге лучше понять, когда выбрано копирование и не выполняется в C ++ 17.

Проблема показана в фрагменте кода ниже. Функция, объявленная с типом автоматического возврата, который возвращает именованный объект класса, имеет дополнительную конструкцию копирования в своем теле. Если возвращение перекодируется для возврата неназванного временного объекта, происходит исключение. Если функция перекодируется для явного возврата экземпляра класса (вместо auto), происходит исключение.

Если структура 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 с неопубликованной копией. Все остальные варианты генерируют полностью проверенный код. Копия также удаляется, если у класса А нет параметров шаблона.

Эта ссылка на Godbolt показывает код, сгенерированный GCC, clang и msvc: https://www.godbolt.org/z/FDAvQO.

Возможно, это просто ошибка / пропущенная возможность оптимизации, которую упускает кланг, а бренды G и M - нет. Если это так, я постараюсь найти подходящее место, чтобы опубликовать это, чтобы люди из Clang исправили это. Но я чувствую, что здесь может происходить что-то более глубокое, например, фундаментальная разница между возвращением auto и возвращением шаблонного объекта класса. Я полагаю, что C ++ 17 гарантирует, что безымянный-RVO всегда будет происходить, но этот-RVO, как в моем случае, не гарантирован - я хотел бы понять, почему это так (и почему это применимо здесь).

1 Ответ

2 голосов
/ 22 мая 2019

Как вы и подозревали, компилятору не требуется удалять копию здесь, так что это скорее "упущенная возможность"

[class.copy.elision] говорит, что в этом сценарии компилятору разрешено для исключения, но не обязательно.

[...] Это исключение операций копирования / перемещения, называемое copy elision , допускается при следующих обстоятельствах (которые могут быть объединены для устранения нескольких копий):
- в операторе return в функции с типом возврата класса, когда выражение является именем энергонезависимый автоматический объект [...] того же типа (игнорируя квалификацию cv), что и функция возвращаемый тип, операция копирования / перемещения может быть опущена путем непосредственного создания автоматического объекта в возвращаемый объект вызова функции

...