Почему clang не оптимизирует это с NRVO?

Я пытаюсь понять, почему достаточно хороший компилятор C++ 11 (clang) не оптимизирует этот код, и интересно, есть ли у кого-нибудь здесь мнения.

#include <iostream>
#define SLOW

struct A {
  A() {}
  ~A() { std::cout << "A d'torn"; }
  A(const A&) { std::cout << "A copyn"; }
  A(A&&) { std::cout << "A moven"; }
  A &operator =(A) { std::cout << "A copy assignmentn"; 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, но это, казалось бы, такой распространенный простой случай.

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

2 ответов


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

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

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

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


копия, которую вы видите в медленном пути, вызвана не отсутствием РВО, а тем, что в B (MakeA ())" MakeA () "является rvalue, но в B (a)" a " является lvalue.

чтобы сделать это ясно, давайте изменим медленный путь, чтобы указать, где 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());

таким образом, РВО произошло.

исправление, которое удаляет все копии, является:

return B(std::move(a));