Почему Clang не оптимизирует это с 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

Что, очевидно, не так приятно. Итак, вопрос:

Почему я не вижу оптимизации NRVO, примененной в случае "МЕДЛЕННО"? Я знаю, что компилятору не требуется применять NRVO, но это может показаться довольно распространенным простым случаем.

В общем, я стараюсь поощрять код в стиле "SLOW", потому что мне гораздо легче отлаживать.

2 ответа

Решение

Ответ прост: потому что в этом случае не разрешается применять elision. Компилятору разрешено применять только в очень редких и особых случаях применять разрешение на копирование. Цитата из стандарта 12.8 [class.copy] параграф 31:

... Это исключение операций копирования / перемещения, называемых разрешением копирования, допускается при следующих обстоятельствах (которые могут быть объединены для устранения нескольких копий):

  • в операторе return в функции с типом возврата класса, когда выражение является именем энергонезависимого автоматического объекта (кроме параметра функции или catch-clause) с тем же типом cv unqualified, что и тип возврата функции, Операция копирования / перемещения может быть опущена путем создания автоматического объекта непосредственно в возвращаемое значение функции
  • [...]

Очевидно, тип B(a) не является Aкопирование не разрешено. Другие пули в том же пункте относятся к таким вещам, как throw выражения, дубликаты из временной и декларации исключений. Ни один из них не применяется.

Копия, которую вы видите на медленном пути, вызвана не отсутствием RVO, а тем, что в B(MakeA()) "MakeA()" является r-значением, а в 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));
Другие вопросы по тегам